Confinity Documentation
  • Latest Version
  • Latest Version
  • Getting Started

    • Introduction
    • Core Concepts
    • Create an Application
    • Glossary
  • Essentials

    • Authentication & SSO
    • Breaking Changes
    • Roslyn Source Analyzers
    • Changelog
    • ConfinityContent
    • ConfinitySelectable
    • Confinity Schedules
    • Data Seeding
    • Development guidelines [WIP]
    • Entity App
    • Entity Form
    • Entity Permissions
    • Frontend Configuration
    • Images
    • Known Issues
    • Localization
    • Migrations
    • Modules [WIP]
    • On-Site Editing
    • Settings
    • Cascade Delete
    • Replication
    • Infrastructure
  • Modules

    • Analytics Module
    • Assets Module
    • Blog Module
    • Cookie Consent Module
    • Forms Module
    • Friendly Captcha (Forms Module )
    • GeoIP Module
    • Htmx
    • Mail Module
    • Mailing Module
    • MediaPlayer Module
    • GoogleMyBusiness Module
    • OpenTelemetry Module
    • Pages Module [WIP]
    • Pattern Library Module
    • SIX Saferpay (worldline) Module
    • Products Module
    • Search Module
    • Wizard Module
  • Guides

    • Create a Custom Entity App Form Element
    • Date and Time
    • Entity Change Listener
    • File Upload / Temp File
    • HTTP security headers
    • conventions [WIP]
    • How to use Confinity with nginx
    • Notifications
    • Nullability
    • Rename Entity
    • Schedules
    • Useful snippets
    • Content Localization
  • Design Guidelines

    • Introduction
    • Page Components
    • Forms Module

File Upload / Temp File

When a form contains a file upload, you might want to store this file temporarly. Confinity offers a service for temporary files to simplify this process.

Warning

Use caution when providing users with the ability to upload files to a server. Attackers may attempt to:

  • Execute denial of service attacks.
  • Upload viruses or malware.
  • Compromise networks and servers in other ways (precautions made by Confinity).

Confinity offers a simplified and relatively secure upload. But still DDOS attacks are not handled.

Configuration in code

Register your space and configure it in code:


// Provide your antivirus scanner for security scanning (required).
module.Services.AddTransient<IAntivirusScanner, MyAwesomeAntivirus>();

module.Configure.RegisterTempFileSpace("upload-doc", options =>
{
    options
        .SpaceCapacity(25 * 1024 * 1024)
        .SessionCapacity(2 * 1024 * 1024)
        .FileCapacity(1 * 1024 * 1024)
        .MaxAge(TimeSpan.FromSeconds(30))
        .AllowFileTypeZip()
        .AllowFileTypeJpg()
        .AllowFileTypePng()
        .AllowFileType(".mp3",
        [
            [0xFF, 0xFB],
            [0xFF, 0xF3],
            [0xFF, 0xF2],
            [0x49, 0x44, 0x33],
        ]);
});

Configuration in code & app-settings

Register your space and configure in app-settings:


// Provide your antivirus scanner for security scanning (required).
module.Services.AddTransient<IAntivirusScanner, MyAwesomeAntivirus>();

module.Configure.RegisterTempFileSpace("upload-doc");

app-settings:

{
  "ConfinityTempFile": {
    "upload-doc": {
      "SpaceCapacityInBytes": 209715200,
      "SessionCapacityInBytes": 20971520,
      "FileCapacityInBytes": 4194304,
      "MaxAgeInSeconds": 20,
      "PermittedFileSignatures": {"pdf":[[37,80,68,70,45]]}
    }
  }
}
Configuration KeyDescription
SpaceCapacityInBytesHow much storage this space can take. This is measured over all current uploads, which have not reached the max age.
SessionCapacityInBytesHow many bytes can be stored in a session.
FileCapacityInBytesHow big a single file can be.
MaxAgeInSecondsHow long a file is allowed to live without a keep alive. This shouldn't be to big so that old sessions can be cleaned up.
AllowedFileTypesAn object of allowed file types.
key: Extension with leading dot.
value: a list of allowed magic byte sequences.
Example: ".jpg": "[[255,216,255,224],[255,216,255,225],[255,216,255,226],[255,216,255,227],[255,216,255,254]]"

Usage

For the temp file to work you need a controller and a kind of a view. This way you can customize the needed authorization.

Controller

The controller needs to implement these three methods:

  • File (route 'file') which allows file uploads
  • Keep (route 'keep') which prevent automatic deletion when the user is idle
  • Remove (route 'remove') which removes an uploaded file

To assist you should implement ITempFileController.

Your implementation should look like this:


private readonly ITempFileService _tempFileService;

public TempFileDocController(ITempFileService tempFileService)
{
    _tempFileService = tempFileService;
}

[HttpPost("file")]
[RequestSizeLimit(ITempFileService.RequestSizeLimit)]
[RequestFormLimits(MultipartBodyLengthLimit = ITempFileService.MaxChunkSize)]
[ProducesResponseType(typeof(TempFileChunkUpload), (int)HttpStatusCode.OK)]
public async Task<IActionResult> File([FromForm(Name = "file")] IFormFile file, [FromForm] int chunkId,
    [FromForm] string sessionId, [FromForm] Guid? fileId, [FromForm] string? contextPayload = null)
{
    var result =
        await _tempFileService.StoreAsync(sessionId, "upload-doc", file, chunkId, fileId);

    return Ok(result);
}

[HttpPost("keep")]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<IActionResult> Keep([FromForm] string sessionId)
{
    await _tempFileService.KeepAliveAsync(sessionId, "upload-doc");
    return Ok();
}

[HttpPost("remove")]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<IActionResult> Remove([FromForm] string sessionId, [FromForm] Guid fileId)
{
    await _tempFileService.RemoveAsync(sessionId, "upload-doc", fileId);
    return Ok();
}

View

In the view you have to request a Session Id. This is done by calling ITempFileService.GenerateSessionId().

For the upload you can use our web component or just use the javascript client.

Web Component (easy but not everything is customizable)

  1. use the component in your html:
*@
    <confinity-file-upload label="Dateien auswählen" api="/api/v1/upload/demo" sessionId="@_tempFileService.GenerateSessionId()"></confinity-file-upload>
@* 

  1. use our javascript:
*@
/** Embed these two web components 
* customElements.define('confinity-file-upload-item', ConfinityFileUploadItem);
* customElements.define('confinity-file-upload', ConfinityFileUpload);
*/
var u=Object.defineProperty;var v=(l,t,e)=>t in l?u(l,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):l[t]=e;var i=(l,t,e)=>(v(l,typeof t!="symbol"?t+"":t,e),e);import{F as p,T as f}from"./TempFileClient.js";function w(l){const t=l<0,e=["B","KB","MB","GB","TB","PB","EB","ZB","YB"];if(t&&(l=-l),l<1)return(t?"-":"")+l+" B";const s=Math.min(Math.floor(Math.log(l)/Math.log(1e3)),e.length-1);l=l/Math.pow(1e3,s);const r=e[s];return(t?"-":"")+l.toFixed(1)+" "+r}const m=class extends HTMLElement{constructor(t,e,s){super();i(this,"_name");i(this,"filenameAttr");i(this,"sizeAttr");i(this,"_size");i(this,"errorEl");i(this,"stateEl");i(this,"progressEl");i(this,"tempFileId");i(this,"_file");i(this,"iconCheck",'<svg viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"><circle cx="16" cy="16" r="15"/><polyline points="7 19 11 23 25 9"/></g></svg>');i(this,"iconError",'<svg fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8.6 1c1.6.1 3.1.9 4.2 2 1.3 1.4 2 3.1 2 5.1 0 1.6-.6 3.1-1.6 4.4-1 1.2-2.4 2.1-4 2.4-1.6.3-3.2.1-4.6-.7-1.4-.8-2.5-2-3.1-3.5C.9 9.2.8 7.5 1.3 6c.5-1.6 1.4-2.9 2.8-3.8C5.4 1.3 7 .9 8.6 1zm.5 12.9c1.3-.3 2.5-1 3.4-2.1.8-1.1 1.3-2.4 1.2-3.8 0-1.6-.6-3.2-1.7-4.3-1-1-2.2-1.6-3.6-1.7-1.3-.1-2.7.2-3.8 1-1.1.8-1.9 1.9-2.3 3.3-.4 1.3-.4 2.7.2 4 .6 1.3 1.5 2.3 2.7 3 1.2.7 2.6.9 3.9.6zM7.9 7.5L10.3 5l.7.7-2.4 2.5 2.4 2.5-.7.7-2.4-2.5-2.4 2.5-.7-.7 2.4-2.5-2.4-2.5.7-.7 2.4 2.5z" clip-rule="evenodd" fill-rule="evenodd"/></svg>');i(this,"iconNew",'<svg viewBox="0 0 485 485" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="85.285" y="192.5" width="200" height="30"/><path d="m350.28 227.02v-118.23l-108.79-108.79h-221.21v445h221.52c23.578 24.635 56.766 40 93.478 40 71.368 0 129.43-58.062 129.43-129.43 0-66.294-50.103-121.1-114.43-128.56zm-100-175.8 48.787 48.787h-48.787v-48.787zm-200 363.79v-385h170v100h100v97.015c-28.908 3.352-54.941 16.262-74.846 35.485h-160.15v30h137.01c-6.865 12.25-11.802 25.719-14.382 40h-122.63v30h120.76c0.999 18.842 6.049 36.626 14.289 52.5h-170.05zm285 40c-54.826 0-99.43-44.604-99.43-99.43s44.604-99.429 99.43-99.429 99.43 44.604 99.43 99.429-44.604 99.43-99.43 99.43z"/><polygon points="350.28 293.96 320.28 293.96 320.28 340.57 273.67 340.57 273.67 370.57 320.28 370.57 320.28 417.19 350.28 417.19 350.28 370.57 396.9 370.57 396.9 340.57 350.28 340.57"/></svg>');i(this,"iconUploading",'<svg viewBox="0 0 485 485" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="85.285" y="192.5" width="200" height="30"/><path d="m350.28 227.02v-118.23l-108.79-108.79h-221.21v445h221.52c23.578 24.635 56.766 40 93.478 40 71.368 0 129.43-58.062 129.43-129.43 0-66.294-50.103-121.1-114.43-128.56zm-100-175.8 48.787 48.787h-48.787v-48.787zm-200 363.79v-385h170v100h100v97.015c-28.908 3.352-54.941 16.262-74.846 35.485h-160.15v30h137.01c-6.865 12.25-11.802 25.719-14.382 40h-122.63v30h120.76c0.999 18.842 6.049 36.626 14.289 52.5h-170.05zm285 40c-54.826 0-99.43-44.604-99.43-99.43s44.604-99.429 99.43-99.429 99.43 44.604 99.43 99.429-44.604 99.43-99.43 99.43z"/><path d="m334.82 278.39c-19.648 21.433-40.591 41.625-61.145 62.179v30l46.61-46.706v93.326h30v-94.302l46.62 47.682v-30c-20.724-20.698-41.444-41.399-62.085-62.179z"/></svg>');i(this,"_inputName");var g;this._inputName=e;const r=document.createElement("div");r.style.position="relative";const n=document.createElement("div");n.style.display="flex",n.style.justifyContent="space-between",n.style.whiteSpace="break-word",this.stateEl=document.createElement("div"),n.appendChild(this.stateEl),this.filenameAttr=document.createElement("div");const a=this.getAttribute(m.AttrFile);this.filenameAttr.textContent=(g=a!=null?a:t==null?void 0:t.name)!=null?g:"",this.filenameAttr.classList.add(m.AttrFile),this.filenameAttr.style.overflow="hidden",this.filenameAttr.style.wordBreak="break-all",this.filenameAttr.style.flexGrow="1",n.appendChild(this.filenameAttr);const o=document.createElement("span");this.sizeAttr=document.createElement("span"),this.sizeAttr.classList.add(m.AttrSize),this._file=t,this.size=t.size,o.style.whiteSpace="nowrap",o.style.paddingLeft=".5rem",o.style.textAlign="right",o.appendChild(this.sizeAttr);const h=document.createElement("span");h.style.paddingLeft=".25rem",h.innerText="X",h.style.color="var(--f-color-error)",h.style.cursor="pointer";const d=s!=null?s:h;d.addEventListener("click",()=>{const E=new CustomEvent("remove-file",{detail:this,bubbles:!0,cancelable:!1,composed:!0});this.dispatchEvent(E)},!1),o.appendChild(d),n.appendChild(o),r.appendChild(n),this.progressEl=document.createElement("div"),this.progressEl.style.position="absolute",this.progressEl.style.bottom=".125rem",this.progressEl.style.left="0",this.progressEl.style.width="0",this.progressEl.style.height=".125rem",this.progressEl.style.marginBottom=".125rem",this.progressEl.style.transition="width ease 1s, opacity ease 2s",this.progressEl.style.backgroundColor="var(--f-color-highlight)",this.progressEl.style.color="var(--f-color-on-highlight);",r.appendChild(this.progressEl),this.errorEl=document.createElement("div"),r.appendChild(this.errorEl),this.attachShadow({mode:"open"}).appendChild(r),this.state=p.NEW}set state(t){this.stateEl.style.paddingRight=".5rem",this.stateEl.style.width="1.5rem",t==p.UPLOADED?this.stateEl.innerHTML=this.iconCheck:t==p.NEW?this.stateEl.innerHTML=this.iconNew:t==p.UPLOADING?this.stateEl.innerHTML=this.iconUploading:t==p.ERROR?this.stateEl.innerHTML=this.iconError:this.stateEl.innerText=t+""}get file(){return this._file}set name(t){this._name=t,this.filenameAttr.innerText=t}set error(t){this.errorEl.innerText=t,this.progressEl.style.opacity="0"}set size(t){this._size=t,this.sizeAttr.innerText=w(t)}set progress(t){this.progressEl.style.opacity="1",this.progressEl.style.width=t*100+"%",t===1&&(this.progressEl.style.opacity="0")}attributeChangedCallback(t,e,s){console.log("attrChange",t,e,s)}onState(t){this.state=t}onProgress(t){this.progress=t}onUploadError(t,e,s){this.error=e}onUploadFinished(t){var s;const e=document.createElement("input");e.type="hidden",e.name=this._inputName,e.value=t.tempFileId+"",(s=this.shadowRoot)==null||s.appendChild(e)}};let c=m;i(c,"AttrSize","size"),i(c,"AttrFile","file");class y extends HTMLElement{constructor(){super();i(this,"files");i(this,"UserDraggingOverClass","userDraggingOver");i(this,"api","");i(this,"sessionId","sessionId");i(this,"maxAgeS",10);i(this,"_inputName","");i(this,"rootEl");i(this,"inputEl");i(this,"labelP");this.files=new f,this.rootEl=document.createElement("label");const t=this.rootEl;t.style.cursor="pointer";const e=document.createElement("input");this.inputEl=e;const s=document.createElement("slot");t.appendChild(e),this.labelP=document.createElement("p"),s.appendChild(this.labelP),this.labelP.innerText="Choose files",t.appendChild(s);const r=this.attachShadow({mode:"open"});r.appendChild(t),r.querySelector("input").addEventListener("change",n=>{const a=n.target;for(let o of a.files)this.add(o);a.value=""}),this.addEventListener("dragover",this.onDragOver),this.addEventListener("dragleave",this.onDragLeave,!1),this.addEventListener("drop",this.onDrop),this.addEventListener("remove-file",this.onRemoveFile)}connectedCallback(){var r,n,a,o,h,d;console.log("Custom square element added to page.");const t=(r=this.getAttribute("label"))!=null?r:"Choose files",e=this.getAttribute("multiple")!==void 0;this.api=(n=this.getAttribute("api"))!=null?n:"",this.sessionId=(a=this.getAttribute("sessionId"))!=null?a:"",this.maxAgeS=parseInt((o=this.getAttribute("maxAgeS"))!=null?o:"60");const s=parseInt((h=this.getAttribute("concurrent"))!=null?h:"2");this._inputName=(d=this.getAttribute("input-name"))!=null?d:"confinity-temp-file",e&&this.inputEl.setAttribute("multiple","multiple"),this.inputEl.setAttribute("type","file"),this.inputEl.style.display="none",this.inputEl.name=this._inputName,this.files=new f({api:this.api,concurrent:s,maxAgeS:this.maxAgeS,sessionId:this.sessionId}),this.labelP.innerText=t}onRemoveFile(t){var s;const e=t.detail;this.files.remove(e.file),(s=this.shadowRoot)==null||s.removeChild(e)}add(t){var e;if(!this.files.contains(t)){const s=this.files.add(t),r=new c(s,this._inputName);s.listener=r,this.append(r),(e=this.shadowRoot)==null||e.appendChild(r)}}onDragLeave(){this.classList.remove(this.UserDraggingOverClass)}onDragOver(t){t.preventDefault(),this.classList.add(this.UserDraggingOverClass)}onDrop(t){if(t.preventDefault(),this.classList.remove(this.UserDraggingOverClass),t.dataTransfer!=null)if(t.dataTransfer.items){for(let e=0;e<t.dataTransfer.items.length;e++)if(t.dataTransfer.items[e].kind==="file"){const s=t.dataTransfer.items[e].getAsFile();if(s==null)return;this.add(s)}}else for(let e=0;e<t.dataTransfer.files.length;e++)this.add(t.dataTransfer.files[e])}}customElements.define("confinity-file-upload-item",c);customElements.define("confinity-file-upload",y);
@*

The web component will add the uploaded files as hidden input to the DOM so that the form-submit adds them. The value is the TempFile.Id.

Required attributes:

  • api: path to your controller
  • sessionId: session id (ITempFileService.GenerateSessionId())

Optional attributes

  • maxAgeS: how long the files are allowed to stay without automatic delete (Default: 60)
  • concurrent: how many files are uploaded concurrent (Default: 2)
  • input-name: what the name is of the hidden input (Default: 'confinity-temp-file')

Stlying

  • The class 'userDraggingOver' is added when the user drags files over the input.

Javascript Client (hard but everything is customizable)

Example usage:

*@    
/** Import the upload client to provider your own upload component.
* The client is accessible from  ConfinityTempFileClient
*/
var U=Object.defineProperty;var g=(n,e,t)=>e in n?U(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var i=(n,e,t)=>(g(n,typeof e!="symbol"?e+"":e,t),t);var r;(function(n){n[n.NEW=10]="NEW",n[n.UPLOADING=20]="UPLOADING",n[n.UPLOADED=30]="UPLOADED",n[n.ERROR=90]="ERROR"})(r||(r={}));const d=class{constructor(e){i(this,"chunks");i(this,"_file");this.chunks=[],this._file=e;const t=Math.ceil(e.size/d.CHUNK_SIZE);for(let s=0;s< t;s++)this.chunks.push(e.slice(s*d.CHUNK_SIZE,Math.min(s*d.CHUNK_SIZE+d.CHUNK_SIZE,e.size),e.type))}get originalFile(){return this._file}};let p=d;i(p,"CHUNK_SIZE",2*1024*1024);class m extends Error{constructor(e,t,s){super(t);i(this,"fileContainer");i(this,"errorName");this.errorName=e,this.fileContainer=s}}class y{constructor(e){i(this,"_state",r.NEW);i(this,"queue");i(this,"_tempFileId");i(this,"listener",null);this.state=r.NEW,this.queue=new p(e)}get tempFileId(){return this._tempFileId}get file(){return this.queue.originalFile}get size(){return this.queue.originalFile.size}get name(){return this.queue.originalFile.name}set state(e){var t;this._state=e,(t=this.listener)==null||t.onState(this._state)}isSame(e){return this.queue.originalFile.name===e.name}async upload(e,t){var o,l;this.state=r.UPLOADING;let s=0;for(const h of this.queue.chunks){const u=new FormData;u.append("file",h,this.name),u.append("chunkId",s+++""),u.append("sessionId",t),this._tempFileId&&u.append("fileId",this._tempFileId),await fetch(e,{method:"POST",body:u}).then(async c=>{var I;if(c.ok){const a=await c.json();this._tempFileId=a.tempFileId}else{this.state=r.ERROR;const a=await c.json(),f=new m(a.errorName,a.message,this);throw(I=this.listener)==null||I.onUploadError(this,a.errorName,a.message),f}return c});const _=this.queue.chunks.length;(o=this.listener)==null||o.onProgress(s/_),this.state=s===_?r.UPLOADED:r.UPLOADING}return(l=this.listener)==null||l.onUploadFinished(this),this}async remove(e,t){if(!this._tempFileId)return;const s=new FormData;s.append("sessionId",t),s.append("fileId",this._tempFileId),await fetch(e,{method:"POST",body:s})}}class F{constructor(e){i(this,"_api");i(this,"_sessionId");i(this,"_maxAgeS");i(this,"files",[]);i(this,"_totalBytes",0);i(this,"_toUpload",[]);i(this,"_concurrent");i(this,"_currentUpload",0);i(this,"uploadingFiles",!1);i(this,"keepInterval",0);i(this,"listener");var t,s,o,l;this._api=(t=e==null?void 0:e.api)!=null?t:"",this._sessionId=(s=e==null?void 0:e.sessionId)!=null?s:"",this._maxAgeS=(o=e==null?void 0:e.maxAgeS)!=null?o:60,this._concurrent=(l=e==null?void 0:e.concurrent)!=null?l:2}add(e){var s;const t=new y(e);return this.files.push(t),this.totalBytes+=t.size,this.addToUploadQueue(t),(s=this.listener)==null||s.onAdd(t),t}set totalBytes(e){var t;this._totalBytes=e,(t=this.listener)==null||t.onTotalBytes(this._totalBytes)}get totalBytes(){return this._totalBytes}contains(e){return this.files.findIndex(t=>t.isSame(e))!==-1}remove(e){var o,l;const t=this.files.findIndex(h=>h.isSame(e.file));if(t!==-1){const h=this.files.splice(t,1)[0];h.remove(this._api+"/remove",this._sessionId),this.totalBytes-=h.size,(o=this.listener)==null||o.onRemove(e)}const s=this._toUpload.findIndex(h=>h.isSame(e.file));s!==-1&&this._toUpload.splice(s,1),(l=this.listener)==null||l.onTotalBytes(this._totalBytes)}addToUploadQueue(e){this._toUpload.push(e),this.triggerUpload()}async triggerUpload(){if(this.keepFiles(),this.uploadingFiles=!0,this._currentUpload< this._concurrent && this._toUpload.length>0){this._currentUpload++;const e=this._toUpload.pop();if(e){try{await this.upload(e)}catch(t){console.warn(t)}this._currentUpload--}await this.triggerUpload()}}async upload(e){return await e.upload(this._api+"/file",this._sessionId)}keepFiles(){!this.keepInterval && this.uploadingFiles&&(this.keepInterval=window.setInterval(async()=>{const e=new FormData;e.append("sessionId",this._sessionId),await fetch(this._api+"/keep",{method:"POST",body:e})},this._maxAgeS/3*2*1e3))}}export{r as F,F as T};window.ConfinityTempFileClient=T;

new ConfinityTempFileClient(...
@*

To use the Javascript client the following implemented interfaces should help:

  1. Create a client of ConfinityTempFileClient

const client = new ConfinityTempFileClient({params})

import {IFilesContainerListener} from './IFilesContainerListener';
import {FileContainer} from './FileContainer';

export interface IConfinityTempFileClient {
    /**
     * Implementation of your listener to react on events.
     */
    listener?: IFilesContainerListener;

    /**
     * Add a file for uploading and starts the upload.
     * @param file the created FileContainer. Set the 'listener' property to react on changes. Your listener should implement IFileContainer.
     * @param contextPayload some optional context payload.
     */
    add(file: File, contextPayload?: any): FileContainer;

    /**
     * Checks if a file is already added.
     * @param file
     */
    contains(file: File): boolean;

    /**
     * Removes an existing file and cancels uploads
     * @param fileContainer
     */
    remove(fileContainer: FileContainer): void;

    /**
     * Returns all files
     */
    files(): FileContainer[];
}

  1. The client takes the following configuration object:
export interface IConfinityTempFileParams {
    /**
     * Path to the controller
     */
    api: string;
    /**
     * The sessionId provided by the ITempFileService
     */
    sessionId: string;
    /**
     * The max age of a file as configured in the module
     */
    maxAgeS?: number;
    /**
     * How many concurrent uploads will take place at once.
     */
    concurrent?: number;
}

  1. Set a listener on the client to react on events.

client.listener = { onTotalBytes: (total) => {console.log('total bytes: ' + total);}

import {FileContainer} from "./FileContainer";

export interface IFilesContainerListener {
    /**
     * Gets calles when the total bytes changes.
     * @param totalBytes
     */
    onTotalBytes(totalBytes: number): void

    /**
     * Gets called, when a file is added.
     * @param fileContainer
     */
    onAdd(fileContainer: FileContainer): void

    /**
     * Get called when a file is removed.
     * @param fileContainer
     */
    onRemove(fileContainer: FileContainer): void
}

  1. Set a listener on the added file to react on its events.
const file = client.add(myFile);
file.listener = {
    onUploadFinished: (fileContainer) => {
        console.log('upload finished!')
    }
}
import {FileContainerState} from "./FileContainerState";
import {FileContainer} from "./FileContainer";

export interface IFileContainer {
    /**
     * Is called when the state changes. 
     * @param value
     */
    onState(value: FileContainerState): void;

    /**
     * Called when progress is changed. 
     * @param value 0..1 of upload
     */
    onProgress(value: number): void;

    /**
     * Called when the upload has finished
     * @param fileContainer
     */
    onUploadFinished(fileContainer: FileContainer): void

    /**
     * Called when the upload had an error.
     * @param fileContainer
     * @param errorName
     * @param errorDescription
     */
    onUploadError(fileContainer: FileContainer, errorName: string, errorDescription: string): void
}

Prev
Entity Change Listener
Next
HTTP security headers