Coder Social home page Coder Social logo

kukhariev / ngx-uploadx Goto Github PK

View Code? Open in Web Editor NEW
42.0 4.0 23.0 2.95 MB

Angular Resumable Upload Module

Home Page: https://github.com/kukhariev/ngx-uploadx

License: MIT License

TypeScript 84.59% JavaScript 5.00% HTML 7.29% SCSS 3.03% Shell 0.09%
uploader angular chunked-uploads tus resumable-upload uploadx

ngx-uploadx's Introduction

ngx-uploadx

Angular Resumable Upload Module

tests build npm version commits since latest release

Key Features

  • Pause / Resume / Cancel Uploads
  • Retries using exponential back-off strategy
  • Chunking

Basic usage

  • Add ngx-uploadx module as dependency:
  npm install ngx-uploadx
  • Import UploadxModule:
//...
import { UploadxModule } from 'ngx-uploadx';

@NgModule({
  imports: [
    UploadxModule,
   // ...
});
  • Add uploadx directive to the component template and provide the required options:
// Component code
//...
import { UploadxOptions, UploadState } from 'ngx-uploadx';

@Component({
  selector: 'app-home',
  templateUrl: `
  <input type="file" [uploadx]="options" (state)="onUpload($event)">
  `
})
export class AppHomeComponent {
  options: UploadxOptions = { endpoint: `[URL]` };
  onUpload(state: UploadState) {
    console.log(state);
    //...
  }
}

Please navigate to the src/app sub-folder for more detailed examples.

Server-side setup

API

UploadxOptions

  • allowedTypes Allowed file types (directive only)

  • authorize Function used to authorize requests (example)

  • autoUpload Auto start upload when files added. Default value: true

  • storeIncompleteHours Limit upload lifetime. Default value: 24

  • chunkSize Fixed chunk size. If not specified, the optimal size will be automatically adjusted based on the network speed. If set to 0, normal unloading will be used instead of chunked

  • maxChunkSize Dynamic chunk size limit

  • concurrency Set the maximum parallel uploads. Default value: 2

  • endpoint URL to create new uploads. Default value: '/upload'

  • responseType Expected server response type

  • headers Headers to be appended to each HTTP request

  • metadata Custom metadata to be added to the uploaded files

  • multiple Allow selecting multiple files. Default value: true (directive only)

  • prerequest Function called before every request (example)

  • retryConfig Object to configure retry settings:

    • maxAttempts Maximum number of retry attempts. Default value: 8
    • shouldRestartCodes Upload not exist and will be restarted. Default value: [404, 410]
    • authErrorCodes If one of these codes is received, the request will be repeated with an updated authorization token. Default value: [401]
    • shouldRetryCodes Retryable 4xx status codes. Default value: [408, 423, 429, 460]
    • shouldRetry Overrides the built-in function that determines whether the operation should be retried
    • minDelay Minimum (initial) retry interval. Default value: 500
    • maxDelay Maximum retry interval. Default value: 50_000
    • onBusyDelay Delay used between retries for non-error responses with missing range/offset. Default value: 1000
    • timeout Time interval after which unfinished requests must be retried
    • keepPartial Determines whether partial chunks should be kept
  • token Authorization token as a string or function returning a string or Promise<string>

  • uploaderClass Upload API implementation. Built-in: UploaderX(default), Tus. More examples

UploadxModule

Adds directives and provide static method withConfig for global configuration (example).

๐Ÿ’ก No need to import UploadxModule if you do not use the uploadx or uploadxDrop directives in your application.

Directives

<div uploadxDrop>
  <label class="file-drop">
    <input type="file" [uploadx]="options" [control]="control" (state)="onState($event)" />
  </label>
</div>

uploadx

File input directive.

selectors: [uploadx], uploadx

Properties:

  • @Input() uploadx: UploadxOptions Set directive options

  • @Input() options: UploadxOptions Alias for uploadx property

  • @Input() control: UploadxControlEvent Control the uploads

  • @Output() state: EventEmitter<UploadState> Event emitted on upload state change

uploadxDrop

File drop directive.

selector: uploadxDrop

๐Ÿ’ก Activates the .uploadx-drop-active class on DnD operations.

UploadxService

  • init(options?: UploadxOptions): Observable<UploadState>

    Initializes service. Returns Observable that emits a new value on progress or status changes.

    //  @example:
    uploadxOptions: UploadxOptions = {
      concurrency: 4,
      endpoint: `${environment.api}/upload`,
      uploaderClass: Tus
    };
    ngOnInit() {
      this.uploadService.init(this.uploadxOptions)
        .subscribe((state: UploadState) => {
          console.log(state);
          // ...
        }
    }
  • connect(options?: UploadxOptions): Observable<Uploader[]>

    Initializes service. Returns Observable that emits the current queue.

    // @example:
    @Component({
      template: `
        <input type="file" uploadx">
        <div *ngFor="let item of uploads$ | async">{{item.name}}</div>
      `,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class UploadsComponent {
      uploads$: Observable<Uploader[]>;
      options: UploadxOptions = {
        endpoint: `${environment.api}/upload?uploadType=uploadx`,
        headers: { 'ngsw-bypass': 1 }
      }
      constructor(private uploadService: UploadxService) {
        this.uploads$ = this.uploadService.connect(this.options);
      }
  • disconnect(): void

    Terminate all uploads and clears the queue.

  • ngOnDestroy(): void

    Called when the service instance is destroyed. Interrupts all uploads and clears the queue and subscriptions.

    ๐Ÿ’ก Normally ngOnDestroy() is never called because UploadxService is an application-wide service, and uploading will continue even after the upload component is destroyed.

  • handleFiles(files: FileList | File | File[], options = {} as UploadxOptions): void

    // @example:
    onFilesSelected(): void {
      this.uploadService.handleFiles(this.fileInput.nativeElement.files);
    }

    Creates uploaders for files and adds them to the upload queue.

  • control(event: UploadxControlEvent): void

    Uploads control.

    // @example:
    pause(uploadId?: string) {
      this.uploadService.control({ action: 'pause', uploadId });
    }
    setToken(token: string) {
      this.uploadService.control({ token });
    }
  • request<T = string>(config: AjaxRequestConfig): Promise<AjaxResponse<T>>

    Make HTTP request with axios like interface. (example)

  • state(): UploadState[]

    Returns the current state of uploads.

    // @example:
    uploads: UploadState[];
    constructor(private uploadService: UploadxService) {
      // restore background uploads
      this.uploads = this.uploadService.state();
    }
  • queue: Uploader[]

    Uploaders array.

    // @example:
    export class UploadComponent {
      state: UploadState;
      options: UploadxOptions = {
        concurrency: 1,
        multiple: false,
        endpoint: `${environment.api}/upload`,
      }
      constructor(private uploadService: UploadxService) {
        this.state = this.uploadService.queue[0] || {};
      }
  • events: Observable<UploadState>

    Uploads state events.

DI tokens

  • UPLOADX_FACTORY_OPTIONS: override default configuration

  • UPLOADX_OPTIONS: global options

  • UPLOADX_AJAX: override internal ajax lib

Demo

Checkout the Demo App or run it on your local machine:

  • Run script npm start
  • Navigate to http://localhost:4200/

Build

Run npm run build:pkg to build the lib.

packaged by ng-packagr

Bugs and Feedback

For bugs, questions and discussions please use the GitHub Issues.

Contributing

Pull requests are welcome!
To contribute, please read contributing instructions.

License

The MIT License (see the LICENSE file for the full text)

ngx-uploadx's People

Contributors

kontrollanten avatar kukhariev avatar maertz avatar philippjenni avatar renovate-bot avatar renovate[bot] avatar smartm0use avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ngx-uploadx's Issues

UploadxService alongside alternate File Input

I'm attempting to use your module (the service specifically) alongside the PrimeNG fileupload component ( https://www.primefaces.org/primeng/#/fileupload ) to give me chunking functionality. It appears to send the initial request to the configured url ( UploadxOptions.url ), but the following PUT requests that happen here:

Uploader.prototype.sendFile = function (start) {
...
xhr.open('PUT', this.url, true);
...
}

have this.url as null. Also tried setting the url in the control methods "itemOptions" parameter, but no luck there.

I'm not sure if this is a bug, or perhaps that am I using the service in a way you didn't intend. Is the intent that the initial POST call and the subsequent PUT calls hit the same apiUrl/method?

Uploading of same file simultaneously in two different tabs of same window is failing

Describe the bug
If we try to upload the same file in two different tabs of same window It considers the 2nd file same as the first and shows the same progress for both(try to upload large file).

To Reproduce
Steps to reproduce the behavior:

  1. download and setup the demo upload pages running.
  2. open localhost:4200 in two different tabs within a window.
  3. try to open a file in first and then the same file in second window.
  4. click pause for few seconds and click upload again.
  5. It will show the same progress on both the pages now.

Expected behavior
Both pages should show actual upload progress.

Setup details:

  • Used ngx-uploadx 4.0.1
  • Used Angular 7.3.10
  • Used Firefox on windows 10
  • Used node-uploadx 3.0.0

Compatibility issue with angular 5.2.0

Hello,
i have installed ngx-uploadx in my angular project, but after installing it, my app stopped working.
After searching the internet i found that this plugin might have issue with my angular version.

in package.json
"@angular/core": "5.2.0", "ngx-uploadx": "^2.4.5",

if to run app with this plugin, if i need to downgrade ngx-uploadx plugin please provide me version number with that i can use this plugin in my angular 5 project

Errors coming on ng serve pasting here:

screen shot 2019-03-08 at 4 12 33 pm
screen shot 2019-03-08 at 4 14 26 pm

IE Compatibility

Hi !

After tests in my app, IE doesn't seems to work with this component.
I modified the 'uploader.ts' file by inverting 'xhr.open(..' lines with 'xhr.responseType = 'json'' lines

import { BackoffRetry } from './backoffRetry';
import { XHRFactory } from './xhrfactory';
import {
  UploadStatus,
  UploadItem,
  UploaderOptions,
  UploadState
} from './interfaces';

const noop = () => {};
/**
 * Implements XHR/CORS Resumable Upload
 * @see
 * https://developers.google.com/drive/v3/web/resumable-upload
 */
export class Uploader implements UploaderOptions {
  headers: any;
  metadata: any;
  method: string;
  mimeType: string;
  name: string;
  progress: number;
  remaining: number;
  response: any;
  size: number;
  speed: number;
  uploadId: string;
  url: any;
  private startTime: number;
  private _status: UploadStatus;
  private retry: BackoffRetry;
  private abort;
  /**
   * Creates an instance of Uploader.
   */
  constructor(private file: File, private options: UploaderOptions) {
	this.uploadId = Math.random()
	  .toString(36)
	  .substring(2, 15);
	this.name = file.name;
	this.size = file.size;
	this.mimeType = file.type || 'application/octet-stream';
	this.status = 'added';
	this.retry = new BackoffRetry();
  }
  /**
   * Set individual file options and add to queue
   */
  configure(item: UploadItem = {}) {
	if (this.status === 'added') {
	  const { metadata, headers } = item;
	  this.metadata = {
		name: this.name,
		mimeType: this.mimeType,
		...this.options.metadata,
		...metadata
	  };
	  this.headers =
		this.options.headers instanceof Function
		  ? this.options.headers(this.file)
		  : { ...this.options.headers, ...headers };
	  this.url = this.options.url;
	  this.method = this.options.method;
	}
	this.status = 'queue';
  }

  set status(s: UploadStatus) {
	this._status = s;
	this.notifyState();
	if (s === 'cancelled' || s === 'paused') {
	  this.abort();
	}
  }
  get status() {
	return this._status;
  }
  /**
   * Emit current state
   */
  private notifyState() {
	const state: UploadState = {
	  file: this.file,
	  name: this.name,
	  progress: this.progress,
	  remaining: this.remaining,
	  response: this.response,
	  size: this.size,
	  speed: this.speed,
	  status: this._status,
	  uploadId: this.uploadId,
	  URI: this.url
	};
	// tick for control events detect
	setTimeout(() => {
	  this.options.subj.next(state);
	});
  }

  private setHeaders(xhr: XMLHttpRequest) {
	if (this.headers) {
	  Object.keys(this.headers).forEach(key =>
		xhr.setRequestHeader(key, this.headers[key])
	  );
	}
  }

  /**
   * Initiate upload
   */
  upload() {
	if (this.status === 'added') {
	  this.configure();
	}
	this.status = 'uploading';
	if (this.progress > 0) {
	  return this.resume();
	}
	const xhr = new XMLHttpRequest();
	xhr.open(this.method, this.options.url, true);
	xhr.responseType = 'json';
	if (!!this.options.withCredentials) {
	  xhr.withCredentials = true;
	}
	this.setHeaders(xhr);
	this.options.token
	  ? xhr.setRequestHeader('Authorization', 'Bearer ' + this.options.token)
	  : noop();
	xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
	xhr.setRequestHeader('X-Upload-Content-Length', this.size.toString());
	xhr.setRequestHeader('X-Upload-Content-Type', this.mimeType);
	xhr.onload = () => {
	  if (xhr.status < 400 && xhr.status > 199) {
		// get secure upload link
		this.url = xhr.getResponseHeader('Location');
		this.startTime = this.startTime || new Date().getTime();
		this.sendFile();
	  } else {
		this.response = xhr.response;
		this.status = 'error';
	  }
	};
	xhr.send(JSON.stringify(this.metadata));
  }
  /**
   * Request upload state after 5xx errors or network failures
   */
  private resume(): void {
	const xhr: XMLHttpRequest = XHRFactory.getInstance();
	xhr.open('PUT', this.url, true);
	if (xhr.responseType !== 'json') {
	  xhr.responseType = 'json';
	}
	if (!!this.options.withCredentials) {
	  xhr.withCredentials = true;
	}
	xhr.setRequestHeader('Content-Range', `bytes */${this.size}`);
	xhr.setRequestHeader('Content-Type', this.mimeType);
	this.setHeaders(xhr);
	const onDataSendError = () => {
	  // 5xx errors or network failures
	  if (xhr.status > 499 || !xhr.status) {
		XHRFactory.release(xhr);
		this.retry.wait().then(() => this.resume());
	  } else {
		// 4xx errors
		this.response = xhr.response || {
		  error: {
			code: xhr.status,
			message: xhr.statusText
		  }
		};
		this.status = 'error';
		XHRFactory.release(xhr);
		this.options.nextFile();
	  }
	};
	const onDataSendSuccess = () => {
	  if (xhr.status === 200 || xhr.status === 201) {
		this.progress = 100;
		this.response = xhr.response;
		this.status = 'complete';
		XHRFactory.release(xhr);
		this.options.nextFile();
	  } else if (xhr.status && xhr.status < 400) {
		const range = +xhr.getResponseHeader('Range').split('-')[1] + 1;
		this.retry.reset();
		XHRFactory.release(xhr);
		this.abort = this.sendFile(range);
	  } else {
		onDataSendError();
	  }
	};
	xhr.onerror = onDataSendError;
	xhr.onload = onDataSendSuccess;
	xhr.send();
  }
  /**
   * Content upload
   */
  private sendFile(start: number = 0): () => void {
	if (this.status === 'cancelled' || this.status === 'paused') {
	  return;
	}
	let end: number = this.options.chunkSize
	  ? start + this.options.chunkSize
	  : this.size;
	end = end > this.size ? this.size : end;
	const chunk: Blob = this.file.slice(start, end);
	const xhr: XMLHttpRequest = XHRFactory.getInstance();

	xhr.open('PUT', this.url, true);
	if (xhr.responseType !== 'json') {
	  xhr.responseType = 'json';
	}

	if (!!this.options.withCredentials) {
	  xhr.withCredentials = true;
	}
	xhr.setRequestHeader(
	  'Content-Range',
	  `bytes ${start}-${end - 1}/${this.size}`
	);
	xhr.setRequestHeader('Content-Type', this.mimeType);
	this.setHeaders(xhr);
	const updateProgress = (pEvent: ProgressEvent) => {
	  const uploaded = pEvent.lengthComputable
		? start + (end - start) * (pEvent.loaded / pEvent.total)
		: start;
	  this.progress = +(uploaded / this.size * 100).toFixed(2);
	  const now = new Date().getTime();
	  this.speed = Math.round(uploaded / (now - this.startTime) * 1000);
	  this.remaining = Math.ceil((this.size - uploaded) / this.speed);
	  this.notifyState();
	};
	const onDataSendError = () => {
	  // 5xx errors or network failures
	  if (xhr.status > 499 || !xhr.status) {
		XHRFactory.release(xhr);
		this.retry.wait().then(() => this.resume());
	  } else {
		// 4xx errors
		this.response = xhr.response || {
		  error: {
			code: +xhr.status,
			message: xhr.statusText
		  }
		};
		this.status = 'error';
		XHRFactory.release(xhr);
		this.options.nextFile();
	  }
	};
	const onDataSendSuccess = () => {
	  if (xhr.status === 200 || xhr.status === 201) {
		this.progress = 100;
		this.response = xhr.response;
		this.status = 'complete';
		XHRFactory.release(xhr);
		this.options.nextFile();
	  } else if (xhr.status && xhr.status < 400) {
		const range = +xhr.getResponseHeader('Range').split('-')[1] + 1;
		this.retry.reset();
		XHRFactory.release(xhr);
		// send next chunk
		this.abort = this.sendFile(range);
	  } else {
		onDataSendError();
	  }
	};
	xhr.onerror = onDataSendError;
	xhr.onload = onDataSendSuccess;
	xhr.upload.onprogress = updateProgress;
	xhr.send(chunk);
	return () => {
	  xhr.abort();
	};
  }
}

Feature request: maxChunkSize

Is your feature request related to a problem? Please describe.
An automated chunkSize, under excellent network conditions, can lead to a chunk being larger than what the backend allows for security reasons and memory constraints.

Describe the solution you'd like
I would like a maxChunkSize option, so that chunkSize can be automatic but never exceed maxChunkSize.

Describe alternatives you've considered
I have been using a fixed chunkSize but that's not optimal.

Thanks for considering!

Infinite retries upon 500 response

Describe the bug
When the server returns 500 upon file upload (PUT request) there's infinite retries.

To Reproduce

    this.uploadService.handleFiles(file, {
      endpoint: `${environment.apiUrl}/api/v1/videos/upload?uploadType=multipart`,
      multiple: false,
      token: this.authService.getAccessToken(),
    })

And then return 500 when the last part has been uploaded.

Expected behavior
A clear and concise description of what you expected to happen.
Setup details:

  • Used ngx-uploadx: 4.0.2
  • Used Angular version: 11.1.1
  • Used browser and OS: Linux, Brave
  • Used server software: Express 4.12.4

Additional context
When I'm debugging the code it looks like line 136 is reached before the server responded with 500.

while (this.status === 'uploading' || this.status === 'retry') {
this.status = 'uploading';
try {
this.url = this.url || (await this.getFileUrl());
this.offset = isNumber(this.offset) ? await this.sendFileContent() : await this.getOffset();
this.retry.reset();

Free the getResponseBody

Is your feature request related to a problem? Please describe.
I'm not able to overwrite the getResponseBody method in my uploaderClass which extends the "Uploader" class.

Describe the solution you'd like
Make getResponseBody public to be able to take full control over the response body parsing on uploader class level.

Describe alternatives you've considered
Handle such behaviour on upload item level (not prefered) :)

Additional context
I've used the MultiPartFormData example within the "upload-examples" to demonstrate this.
image

export class MultiPartFormData extends Uploader {
  responseType = 'json' as XMLHttpRequestResponseType;

  private getResponseBody(xhr: any) { 
    let body: UploadAttachmentFilesResponse[] =
      'response' in xhr ? xhr.response : xhr.responseText;
    if (body && this.responseType === 'json' && typeof body === 'string') {
      try {
        body = JSON.parse(body);
      } catch (_a) {}
    }

    return body
  }

  async getFileUrl(): Promise<string> {
    this.offset = 0;
    const formData: FormData = new FormData();
    formData.append('files', this.file, this.file.name);
    await this.request({
      method: 'POST',
      body: formData,
      url: this.endpoint,
      progress: true
    });
    this.offset = this.size;
    const location = this.getValueFromResponse('location');
    return location ? resolveUrl(location, this.endpoint) : '';
  }

  async sendFileContent(): Promise<number | undefined> {
    return this.size;
  }

  async getOffset(): Promise<number | undefined> {
    return 0;
  }
}

Cheers :)

'cancel' and 'cancelAll' should send delete action to server

Motivation

If an upload is cancelled, it should not have 'zombie' files at the server

Description

If the cancel or cancelAll action is called, the service should send a 'delete' request for each file to same url like the upload is sent. This sould also be sent, if the file is uploaded complete and the file stays in the queue.

Acceptance criteria

  • Incomplete uploads can be deleted at the server side, if the upload is canceled
  • Complete uploaded files that stay in the queue should be removed from the server if the file is removed from the queue.

Tests

  • Upload a file and cancel the file during the upload
  • Upload multiple files and cancel a single file during upload
  • Upload multiple files and cancel all uploads during the upload process
  • Upload a file and cancel the upload with cancelAll during the upload process

3.x -> 4.x upgrade

Version 4.0 includes a number of breaking changes:

  • Dropped support for Angular <7.x. Please upgrade.

  • Removed uploadAll , cancelAll, pauseAll from type UploadAction.

  • Directive properties:

    • @Output() uploadxState: EventEmitter<Observable<UploadState>> has been with
      @Output() state: EventEmitter<UploadState>
    • Renamed @Input() uploadxAction: UploadxControlEvent to @Input() control: UploadxControlEvent

    Before:

    // ngx-uploadx v3.x
    import { Observable } from 'rxjs';
    import { UploadxOptions, UploadState } from 'ngx-uploadx';
    
    @Component({
      selector: 'app-home',
      templateUrl: `
         <input type="file"
            [uploadx]="options"
            (uploadxState)="onUpload($event)"
            [uploadxAction]="control">
         `
    })
    export class AppHomeComponent {
      control: UploadxControlEvent;
      options: UploadxOptions = { endpoint: `[URL]` };
      onUpload(state$: Observable<UploadState>) {
        state$.subscribe((state: UploadState) => {
          console.log(state);
          //...
        });
      }
      cancelAll() {
        this.control = { action: 'cancelAll' };
      }
    }

    After:

    // ngx-uploadx v4.x
    import { UploadxOptions, UploadState } from 'ngx-uploadx';
    
    @Component({
      selector: 'app-home',
      templateUrl: `
          <input type="file"
             [uploadx]="options"
             (state)="onUpload($event)"
             [control]="control">
          `
    })
    export class AppHomeComponent {
      options: UploadxOptions = { endpoint: `[URL]` };
      onUpload(state: UploadState) {
        console.log(state);
        //...
      }
      cancelAll() {
        this.control = { action: 'cancel' };
      }
    }
  • Renamed type RequestParams to RequestOptions.

  • Removed obsolete setAuth() method, use new option authorize instead.

getOffsetFromResponse isn't behaving quite right

in uploaderx.ts, getOffsetFromResponse doesn't seem to be behaving quite right for the following use cases.
Missing Range: this should be allowed, using the same Google Doc upload reference (https://developers.google.com/drive/api/v3/manage-uploads#resume-upload) that you are using. This should tell the service that no content has been received and to start at byte 0.

"Range: bytes=0-1" : This will get matched as "-1" and will send from byte 0. There's not really a standard on using the Range header on a response, so not sure of what the right answer is. Possible solution would be looking for two dashes when matching "-1", ie, "-1--1" for no content. /(--1|\d+)$/g
Also looking for just "-1", like "Range: bytes=-1" could work.

https://github.com/kukhariev/ngx-uploadx/blob/master/src/uploadx/src/uploaderx.ts

uploading the same file should be faster

As far as I know about tus, the client should check if the same file has been uploaded before. if so the upload progress should be fasten and reach 100% in one second.

Here is my observation while running up this demo by "npm start" and uploading the same file several times:

  1. The second uploading is just as a normal uploading, the progress starting from 0% .... to 100% at a normal pace. Check the server side, the file uploaded in last time remains the same size , not truncated to a smaller one. Finally its timestamp is updated.
  2. During the second uploading, I make it offline by using chrome dev tool, the uploading turns into "paused", then I turn it back to "No throttling", the progress go directly to 100%, as sudden as lightning.

Thanks

Please include a Changelog

It's very useful for libraries like this one to include a changelog that helps with migration. I was just upgrading from 3.4 to 3.5 and that has breaking changes that are not documented.

Describe the solution you'd like
A Changelog file with a description of the updates and the breaking changes.

Describe alternatives you've considered
I have to read the code to understand how to migrate my code.

Additional context
Thanks!

"events" in UploadService not called if item in queue is cancelled

Describe the bug
If an upload item in the queue is cancel with the "control" method, the "events" observable is not called.

To Reproduce

  1. Add a file to the queue
  2. Click cancel button and check if "events" Observable is called

Expected behavior
Events should be called on each state change.

Setup details:

  • Used ngx-uploadx 3.3.3
  • Used Angular version 9.0.4

Any good way to rename a queue element?

probably this is a silly question and not the place to put it, but is there any consistent way to rename the file once is in the queue already?

i'm using NGXS to handle the uploads with an application store and i'm copying the content of the uploadService queue into my own thing, however my rename functionality gets overwritten by what it is inside the queue at the time i restart the upload.

the endpoint gets updated properly and the file sent to the server have the proper name, but visually my user will still see the old name.

hope this makes sense since this is not a bug.

i can't copy my code since is a corporate application.

but the state is just a copy of the queue.

i'm using the latest of the library.

Window is not defind with Server Side Rendering

Describe the bug
When prerendering an angular app server side with UploadxModule imported in the ngModule I obtain a window is not defined error. I already tried to polyfill the window obj as suggested for example here: angular/universal#1523
But with no success with ngx-uploadx (I made all other dependencies of my project to work on ssr but failed with ngx-uploadx)

To Reproduce
I basically followed the setup explained here: https://github.com/angular-university/angular-universal-course/tree/master

then when executing the prerendering I obtain the error window is not defined that made the prerendering to fail

Expected behavior
ngx-uploadx should work when the page is prerendered server side or at least silently fail. I'm pretty new to angular ssr so I don't know exactly how ngx-uploadx should behave

Setup details:

  • Used ngx-uploadx 4.0.1
  • Used Angular version 11.0.5

Progress UploadStates no longer emitted

Describe the bug
This is a strange one, this hasn't been a problem until very recently but also not sure exactly when it began.
Only happens for configuration/AOT built angular (ie ng build --configuration=dev etc.). Doesn't happen when using app via "ng serve".

I use PrimeNG's fileupload component combined with ngx-uploadx UploadService.

Did some simple debugging with console.log messages:

this.options = {
      endpoint: APP_CONFIG.apiEndpoint + '/api/v1/document/docFileUploadInit',
      chunkSize: 2097152,
      concurrency: 5,
      uploaderClass: UploaderXCustom,
      autoUpload: false,
      token: () => this.authService.getToken()
    };
this.uploadService.init(this.options).subscribe((item: UploadState) => {
      console.log(JSON.stringify(item, null, 2));
      ...
});

When local via ng serve (this is where things work normally):

{
  "file": {},
  "name": "dummy2.txt",
  "size": 10485760,
  "status": "added",
  "uploadId": "f02b88a6",
  "url": ""
}
{
  "file": {},
  "name": "dummy2.txt",
  "size": 10485760,
  "status": "queue",
  "uploadId": "f02b88a6",
  "url": ""
}
{
  "file": {},
  "name": "dummy2.txt",
  "responseStatus": 0,
  "size": 10485760,
  "status": "uploading",
  "uploadId": "f02b88a6",
  "url": ""
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 6.41,
  "remaining": 67,
  "responseStatus": 0,
  "size": 10485760,
  "speed": 147798,
  "status": "uploading",
  "uploadId": "f02b88a6",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=51baf61c-9284-47e7-9c3f-137bbc1f8be5"
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 40,
  "remaining": 11,
  "responseStatus": 0,
  "size": 10485760,
  "speed": 582704,
  "status": "uploading",
  "uploadId": "f02b88a6",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=51baf61c-9284-47e7-9c3f-137bbc1f8be5"
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 60,
  "remaining": 7,
  "responseStatus": 0,
  "size": 10485760,
  "speed": 644881,
  "status": "uploading",
  "uploadId": "f02b88a6",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=51baf61c-9284-47e7-9c3f-137bbc1f8be5"
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 80,
  "remaining": 4,
  "responseStatus": 0,
  "size": 10485760,
  "speed": 694479,
  "status": "uploading",
  "uploadId": "f02b88a6",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=51baf61c-9284-47e7-9c3f-137bbc1f8be5"
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 100,
  "remaining": 0,
  "responseStatus": 0,
  "size": 10485760,
  "speed": 729444,
  "status": "uploading",
  "uploadId": "f02b88a6",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=51baf61c-9284-47e7-9c3f-137bbc1f8be5"
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 100,
  "remaining": 0,
  "response": 1274303,
  "responseStatus": 200,
  "size": 10485760,
  "speed": 729444,
  "status": "complete",
  "uploadId": "f02b88a6",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=51baf61c-9284-47e7-9c3f-137bbc1f8be5"
}

When running via angular-http-server on a ng build --configuration=dev build (AOT, etc.):

{
  "file": {},
  "name": "dummy2.txt",
  "size": 10485760,
  "status": "added",
  "uploadId": "182e259e",
  "url": ""
}
{
  "file": {},
  "name": "dummy2.txt",
  "size": 10485760,
  "status": "queue",
  "uploadId": "182e259e",
  "url": ""
}
{
  "file": {},
  "name": "dummy2.txt",
  "responseStatus": 0,
  "size": 10485760,
  "status": "uploading",
  "uploadId": "182e259e",
  "url": ""
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 100,
  "remaining": 0,
  "response": 1274303,
  "responseStatus": 200,
  "size": 10485760,
  "status": "complete",
  "uploadId": "182e259e",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=39aafa11-20e1-469c-80f6-b12831915c59"
}

For some reason the progress events are skipped right over. Also happens on our deployed Azure DEV environment. It otherwise behaves exactly as it should - the same number of requests, responses, chunks, etc. Just no progress states emitted. Basically the only issue is my progress bar UI no longer shows anything. It just sits at 0% until final chunk is sent when it jumps to 100%.

At first I tried reverting to the default uploaderClass (I had it overridden with my own) by not defining one in UploadxOptions - and altered my server responses to be compatible. No luck.

Then I updated ngx-uploadx from 3.1.4 to 3.3.3. No luck.

To Reproduce
Not sure it'd be worth trying to reproduce my exact scenario as it's kind of involved, more looking for advice / whether anyone has seen anything like this.

Setup details:

  • Used ngx-uploadx: 3.1.4 (later updated to 3.3.3)
  • Used Angular version : Angular 8
  • Used browser and OS: Chrome/Windows
  • Used server software .NET WebAPI

Additional context
Strangely seems independent of any relevant version changes/etc., but not 100% sure here.

Handler the authentication server error

When the serve response came with an authentication error the library should handle this error (maybe create a new UploadStatus [error-auth]) for retry the upload when the token is refresh

Chunk timeout

Is your feature request related to a problem? Please describe.
I would like to have a way to specify a certain timeout for a chunk, after which it should be retried.

Describe the solution you'd like
A chunkTimeout option, in seconds. If a PATCH request doesn't get a response from the server in that time, the chunk should be retried.

Describe alternatives you've considered
None.

Additional context
None.

Thanks!

Endpoint protocol automatically changed by the code from `https` to `http` when origin is `http`

Apologies if the following isn't 100% clear. I can clarify further. But in a nutshell the main question is: why does the code change the endpoint from https://... to http://... when the origin is http and is that a bug?

---- Thanks very much for a great piece of software regardless!

In this entire discussion, I am using UploadxService.handleFile() - that's where the URL of the endpoint specified in the options object given to it gets automatically converted from https to http (only when running from an http origin).

When developing / testing the code, I serve my single page app on a local machine server and go to url http://localhost:8100 to test a running of the app.

That app connects to an Uploadx node server that sits behind https protocol, and runs on a URL like:

https://upload.example.com

When the app is served from an https origin, e.g. https://app.example.com - then everything works as expected - the app can access the Uploadx node server with no problem, all works.

But when I develop / test the code and serve the app locally, the origin has protocol http, not https. In that case, for some reason, when using handleFile of the service version of Uploadx (UploadxService), it changes the protocol when making the request, even though the endpoint I specify in options given to handle file is preceded by https. I give handle file() the option endopoint: https://upload.example.com/upload but it actually changes that endpoint and makes the request at a different URL, changing the protocol from https to http, i.e. it goes to endpoint http://upload.example.com/upload instead, because the origin had http in it.

This causes a problem, I need to access the https: endpoint always, not the http but it gets converted automatically in the code. Is there a way to stop it from converting from https to http when the origin is http?

Update
I am noticing in the logs of the server side (Uploadx node server) that the endpoint actually seems to have the protocol stripped when it arrives - here's a line from the Uploadx server log:

<ip#>  - - [20/Sep/2019:21:49:15 +0000] "OPTIONS /upload?uploadType=uploadx&upload_id=52014dbf3fbda4e3cef2f4ec3c0f2c53 ...

So it's not that https gets converted to http but it gets stripped, then http is assumed because of the origin's http that it comes from. It's possible I am not understanding something here, but perhaps if we kept the https:// and did not strip it from the endpoint URL, that would solve the problem?

More specifically, looking at the Network tab of the developer tools in chrome when running the app from origin http://localhost:8100, and uploading one file via handleFile, I see the following sequence of requests (using example.com instead of my actual server):

https://upload.example.com/upload?uploadType=uploadx (status 204)
https://upload.example.com/upload?uploadType=uploadx (status 201)
http://upload.example.com/upload?uploadType=uploadx&upload_id=... (status 301)
http://upload.example.com/upload?uploadType=uploadx&upload_id=... (status 301)
http://upload.example.com/upload?uploadType=uploadx&upload_id=... (status 301)
http://upload.example.com/upload?uploadType=uploadx&upload_id=... (status 301)
http://upload.example.com/upload?uploadType=uploadx&upload_id=... (status 301)
...

i.e. as soon as we see an &upload_id in the URL, it becomes http while it should be https to work for my use case.

How to replicate

  1. Set up an Uploadx node server behind https
  2. Run an app in your browser, locally served from http://localhost* that uploads a file to the server above via UploadxServer.handleFile() - the upload will fail as described above.

Zip or mp3 format can't be uploaded

    const UPLOAD_DIR = `/Users/hkhurshudyan/Desktop/graxanut/server/files`;
    return  {
      directory: UPLOAD_DIR,
      allowMIME: ['video/*', 'image/*', 'application/epub+zip', 'application/zip'],
      path: '/files',
      onComplete: async file => {
        return new Promise((resolve, reject) => {
          resolve('done')
        })
      }
    };
  }
it's for server part and this for frontend
```          allowedTypes: 'image/*, application/epub+zip, application/zip',

It's uploads file but it's size 0 byte and I can open it

Refresh token handling

Hi,

I have a question about access token. When a user is authenticated, a token is sent. On my server, the validity of this token is 5min. Every 5 min, a request to refresh the token is sent to the server.

Is it possible to update the token in the service?
I go through the source code but I don't see anything for it. I would like to send the new token to the service and this token will be used for all following request.

The other thing that will be useful is to retry on failed upload after an error due to an old token.

Is it possible to do something like that with your library? If yes, how?

Thanks in advance for your response

Virginie

[Help wanted] Upload to many different endpoint in the same component

Hi,
Do ngx-uploadx allow upload to different endpoint in the same component? For example:
I have 2 input tags:

<input
          #files
          (click)="files.value = ''"
          type="file"
          [uploadx]="options"  // <--- upload to enpoint 1
          [uploadxAction]="control"
          (uploadxState)="onUpload($event)"
          multiple
          style="display:none"
        />

<input
          #files
          (click)="files.value = ''"
          type="file"
          [uploadx]="options"  // <--- upload to enpoint 2
          [uploadxAction]="control"
          (uploadxState)="onUpload($event)"
          multiple
          style="display:none"
        />

Thanks

Make it work with amazon s3 presigned urls

Is your feature request related to a problem? Please describe.
Uploading files bigger than 5 gb directly to S3 buckets using ngx-uploadx

Describe the solution you'd like
On projects with S3 Storage, you can create a presigned url in order to let the angular client upload their files directly to S3. This saves a lot of time and ressources, since the webserver itself does nothing but creating this presigned url.
(Details: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html#query-string-auth-v4-signing-example)

I would like to use Multipartuploads with ngx-uploadx in that scenario https://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html

Describe alternatives you've considered
no alternatives yet

Additional context

Allow to change the endpoint option to the uploadItem

I'm using the library in a system where the files can be uploaded to more than one endpoint, and is a little bit difficult to change the endpoint for different files, because is not nice to change the service configuration each time i need to upload a file (that what i'm doing now):

  1. define autoUpload as false
options: UploadxOptions = {
    concurrency: 2,
    autoUpload: false,
    chunkSize: 1024 * 256 * 8
  };
  1. this.uploadxService.handleFile(file); // Put the file ready to upload
  2. subscribe and start upload the file
let itemId = null;
return this.uploadsProgress.pipe(
      map((item: UploadState) => {
        if (!itemId && item.status === 'added') {
          itemId = item.uploadId;
          this.uploadxService.control({
            action: 'upload',
            itemOptions: {
              token: 'toekneeeeeeeeeee',
              uploadId: itemId,
              endpoint: apiEndpoint,
              method: 'POST'
            } as UploadItem
          } as UploadxControlEvent);
        }
        return item;
      }),
      filter((item: UploadState) => item.status === 'cancelled' || item.status === 'complete' || item.status === 'error' && item.uploadId === itemId)
    );

this should work but the endpoint is not updated in the configure method in Uploader class:

configure(item = {} as UploadItem): void {
    const { metadata, headers, token } = item;
    this.metadata = {
      name: this.name,
      mimeType: this.mimeType,
      size: this.size,
      ...unfunc(metadata || this.metadata, this.file)
    };
    this.chunkSize = this.options.chunkSize || this.chunkSize;
    this.maxRetryAttempts = this.options.maxRetryAttempts || this.maxRetryAttempts;
    this.refreshToken(token);
    this.headers = { ...this.headers, ...unfunc(headers, this.file) };
  }

Other idea is to return the uploader in the handler method because right now i need to take the uploaderId in the uploadsProgress as an even of the service.

Refresh token seems to be not working

Hello,

I'm sorry but refreshToken control don't work as it was.

Before last update, my code was working as expected but since the update in version 2.6.0, it don't work.

Perhaps I don't use the library as expected. I changed my code to fit your retry development. My actual code is :

        this.uploadsProgress.subscribe((item: UploadState) => {
            if (item.status == 'retry' && item.response.message && item.response.message == "Token not valid") {
                this.authenticationService.refresh().subscribe(() => {
                    this.uploadService.control({
                        action: 'refreshToken',
                        token: localStorage.getItem('accessToken')
                    });
                });
                return;
            } 
           ---
        });

Is it correct?

All upload requests are sent with the same (old) token.
Does the problem come from the library or from my code?

Thanks in advance for your response

Virginie

Uncaught TypeError: Object(...) in Angular7 project

Following setup steps produce such error
ngx-uploadx.js:700 Uncaught TypeError: Object(...) is not a function at ngx-uploadx.js:700 at Object../node_modules/ngx-uploadx/fesm5/ngx-uploadx.js (ngx-uploadx.js:706) at __webpack_require__ (bootstrap:76) at Object../src/app/app.module.ts (app.component.ts:9) at __webpack_require__ (bootstrap:76) at Object../src/main.ts (environment.ts:10) at __webpack_require__ (bootstrap:76) at Object.0 (main.ts:12) at __webpack_require__ (bootstrap:76) at checkDeferredModules (bootstrap:43)

Getting following error when trying to implement ngx-uploadx in angular 7 project.

How is re-uploading resumed?

Hi,

I have cloned this repository and run it up with "npm install", drag and drop a file, it could be uploaded successfully. But in middle of uploading if I refresh the web page (mimic that the laptop is shutdown or browser is closed unexpectedly), re-uploading the same file seems transferring from the head because the progress is starting from 0%. No matter I tried "tus", "service" or "directive" mode in this demo, the same result. What I had thought re-uploading the same file should go from the progress where it is interrupted, is anything misunderstanding?

Thanks

Canceled Files cannot be selected again

You can't select a file again, which upload is canceled.

Follow Tasks to reproduce:

  • Select File -> Upload -> Cancel All (During Upload) -> Select same File again (File not in List)

Update Dependencies (Renovate Bot)

This master issue contains a list of Renovate updates and their statuses.

Pending Approval

These branches will be created by Renovate only once you click their checkbox below.

  • Update dependency aws-sdk to v2.686.0
  • Update dependency jsonwebtoken to v8.5.1
  • Update dependency luxon to v1.24.1
  • Update dependency moment to v2.26.0
  • Update dependency nodemailer to v6.4.8
  • Update dependency objects-to-csv to v1.3.6
  • Update dependency pg to v7.18.2
  • Update linters (eslint, eslint-config-prettier, eslint-plugin-import, eslint-plugin-prettier)
  • Update dependency @sindresorhus/is to v2
  • Update dependency badgen to v3
  • Update dependency fs-extra to v9
  • Update dependency p-all to v3
  • Update dependency pg to v8
  • Update dependency prettier to v2
  • Update got packages (major) (gh-got, got)
  • Update linters (major) (eslint, eslint-config-airbnb-base)

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

  • Update dependency renovate to v20.16.0

Advanced
  • Check this box to trigger a request for Renovate to run again on this repository

cancel or pause Action gives undefined exception

If you call a cancel, cancelAll, pause oder pauseAll Action and the queue item where the action is applied is not in uploading state, there is an error "abort is not function".

The problem is in the "uploader.ts" line: 45. It's look like the the problem is created in line 71. The new object has no abort() function in the state object.

Version 2.2.0
Browser: Chrome

Big files upload error with iOS

Chocobozzz/PeerTube#4382
There is a issue with File API on these devices. The file stops being read after 60 seconds.

protected getChunk(): { start: number; end: number; body: Blob } {
this.chunkSize =
this.options.chunkSize === 0 ? this.size : this.options.chunkSize || DynamicChunk.size;
const start = this.offset || 0;
const end = Math.min(start + this.chunkSize, this.size);
const body = this.file.slice(this.offset, end);
return { start, end, body };
}

pause with uploadId is pausing all the queue

Describe the bug
The pause with uploadId seems to be pausing all the queue.

To Reproduce
calling simply the service passing the uploadId.

this.uploadService.control({ action: 'pause', uploadId });

Expected behavior
Only the element with the uploadId should be paused.
Setup details:

  • Used ngx-uploadx latest
  • Used Angular version 8
  • Used browser and OS, MacOS, Chrome

Action 'uploadAll' doesn't create a session

If you disable autoUpload and use the action 'uploadAll', the url to put the file is always undefined. There is no post request to create a session.

My Options are

this.options = {
      concurrency: 2,
      allowedTypes: '*',
      url: '/services/api/upload/register',
      token: 'someToken',
      autoUpload: false,
      withCredentials: true,
      chunkSize: 1024 * 16 * 8,
      headers: (f: File) => ({
        'Content-Disposition': `filename=${encodeURI(f.name)}`
      })
};

Version 2.2.0

dynamic directive id

Hi
I need many multiple uploader , per my categories.

How can I set directives id dynamically

I used upload directive in *ngFor , but directives id confilicted:

my Code :

 <div *ngFor="let item of categories">
    <h3>{{item.name}}</h3>
    <input
    [attr.id]="item.id"
    (click)="???????????.value = ''"   // <== My problem is here 
    (state)="onStateChanged($event)"
    [control]="control"
    [uploadx]="tusoptions"
    id="{{item.id}}"
    type="file"
    class="file_select"
    />
</div>

What to write in ???????? place?

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: baseBranch not found
Message: The following configured baseBranch could not be found: next

Location Error

image

Hi,

Hoping you could help me with the above issue in snip. Have been able to send initial request to my net core api but on return to angular client, the error in image above is thrown. Have tried adding a "Location" and "Content-Location" header but with no luck. Currently, because of error, request is retried which makes sense. Have you any documentation or information on what this error relates to and what kind of response is expected back from my backend.

Also, was wondering if there is a way for my angular client to pass the chunk index to my backend. ie file has 4 chunks so with each chunk, pass param or header value with chunk index 1 - 4. Apologies for raising this as issue. This is a support query and I couldn't find any decent threads on stackoverflow.

delete Action is not send to server on cancel

The implementation of #20 is not working.

The "_status" (Line 2 in example) variable in the code is not in state "cancelled" if you cancel an upload.
If you cancel an uploading file the is the state 'uploading'. Tested with latest Chrome and Firefox.

        return new Promise(function (resolve, reject) {
            if (_this.URI && _this._status === 'cancelled') {
                /** @type {?} */
                var xhr_2 = XHRFactory.getInstance();
                xhr_2.open('DELETE', _this.URI, true);
                xhr_2.responseType = 'json';
                xhr_2.withCredentials = _this.options.withCredentials;
                _this.setCommonHeaders(xhr_2);
                xhr_2.onload = xhr_2.onerror = function () {
                    _this.responseStatus = xhr_2.status;
                    _this.response = parseJson(xhr_2) || {
                        status: {
                            code: +xhr_2.status,
                            message: xhr_2.statusText
                        }
                    };
                    XHRFactory.release(xhr_2);
                    resolve();
                };
                xhr_2.send();
            }
            else {
                resolve();
            }
        });

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.