Coder Social home page Coder Social logo

Comments (8)

Spiral1401 avatar Spiral1401 commented on June 21, 2024 1

Angular service worker provides a header to allow requests to bypass being handled by it. Mentioned here:
angular/angular#21191

Modified the headers in the sendFileContent() of my custom Uploader class:

const headers = {
      'Content-Type': 'application/octet-stream',
      'Content-Range': `bytes ${this.offset}-${end - 1}/${this.size}`,
      'ngsw-bypass': 'true'
    };

Also (somewhat beside the point) had to edit my serverside web.config "Access-Control-Allow-Headers" to include "ngsw-bypass".

Tested and working, progress events are happening again.

from ngx-uploadx.

kukhariev avatar kukhariev commented on June 21, 2024

Hi @Spiral1401!

I can't reproduce it yet.
Lib throttles progress events with 500 ms window, but at least one event per chunk.
Angular v8.2.14, CLI v8.3.25, aot=true, buildOptimizer=true.

{
  "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"
}

There's no "speed" field , which means that either there's no real content upload or there's something wrong with sendFileContent method.

from ngx-uploadx.

Spiral1401 avatar Spiral1401 commented on June 21, 2024

That's interesting, I didn't catch the missing "speed" field. However like I said the upload is working perfectly otherwise - I can breakpoint and see the contents through the chain - and when it completes I can browse for it in our Azure Storage and download the resultant file.

The problem seems to exist regardless of if I override the default uploaderClass with my own implementation, but here is what mine looks like:

import { Uploader, UploadxOptions } from 'ngx-uploadx';

/**
 * Implements XHR/CORS Resumable Upload
 * @see
 * https://developers.google.com/drive/v3/web/resumable-upload
 */
export class UploaderXCustom extends Uploader {
  constructor(readonly file: File, options: UploadxOptions) {
    super(file, options);
    this.responseType = 'json' as XMLHttpRequestResponseType;
  }

  async getFileUrl(): Promise<string> {
    const headers = {
      'Content-Type': 'application/json; charset=UTF-8',
      'X-Upload-Content-Length': `${this.size}`
      // 'X-Upload-Content-Type': `${this.mimeType}`
    };
    const body = JSON.stringify(this.metadata);
    const _ = await this.request({
      method: 'POST',
      body,
      url: this.endpoint,
      headers
    });
    const location =
      this.responseStatus === 200 && this.getValueFromResponse('location');
    this.offset = this.responseStatus === 201 ? 0 : undefined;
    return this.resolveUrl(location, this.endpoint);
  }

  async sendFileContent(): Promise<number> {
    const end = this.chunkSize
      ? Math.min(this.offset + this.chunkSize, this.size)
      : this.size;
    const body = this.file.slice(this.offset, end);
    const headers = {
      'Content-Type': 'application/octet-stream',
      'Content-Range': `bytes ${this.offset}-${end - 1}/${this.size}`
    };
    const _ = await this.request({
      method: 'PUT',
      body,
      url: this.url,
      headers
    });
    console.log('req headers: ' + JSON.stringify(headers, null, 2));
    return this.getOffsetFromResponse();
  }

  async getOffset(): Promise<number> {
    const headers = {
      'Content-Type': 'application/octet-stream',
      'Content-Range': `bytes */${this.size}`
    };
    const _ = await this.request({
      method: 'PUT',
      url: this.url,
      headers
    });
    return this.getOffsetFromResponse();
  }

  protected getOffsetFromResponse() {
    if (this.responseStatus === 308) {
      const str = this.getValueFromResponseCust('Range');
      if (!str) {
        return 0;
      }
      const [match] = str && str.match(/(-1|\d+)$/g);
      return match && +match + 1;
    } else if (this.responseStatus === 200) {
      return this.size;
    }
    return;
  }
  protected onCancel(): void {
    // this.request({ method: 'DELETE' });
  }

  protected setAuth(token: string) {
    this.headers.Authorization = `Bearer ${token}`;
  }

  protected resolveUrl(url, baseURI) {
    if (
      url.indexOf('//') * url.indexOf('https://') * url.indexOf('http://') ===
      0
    ) {
      return url;
    }
    try {
      const res = new URL(url, baseURI).href;
      return res;
    } catch (_a) {
      if (url.indexOf('/') === 0) {
        const matches = baseURI.match(/^(?:https?:)?(?:\/\/)?([^\/\?]+)/g);
        const origin = matches && matches[0];
        return origin + url;
      } else {
        const matches = baseURI.match(
          /^(?:https?:)?(?:\/\/)?([^\/\?]+)?(.*\/)/g
        );
        const path = matches && matches[0];
        return path + url;
      }
    }
  }

  protected getValueFromResponseCust(key) {
    const value = this._xhr.getResponseHeader(key);
    const headers = this._xhr.getAllResponseHeaders();
    console.log('resp headers: ' + JSON.stringify(headers, null, 2));
    if (!value) {
      return null;
    }
    return value;
  }
}

It was based on an earlier version of the library default uploader. My server response is set up to send back a 308 response with no "Range" header when ngx-uploadx is asking for the offset (and there isn't one). Hence why I do what I do in "getValueFromResponseCust". But I can also make the server send back "Range: bytes=0--1" for that scenario and allow it to work with the default class rather than my version.

Here's what the logging looks like for my uploader class and it's console.log statements:

{
  "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": ""
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\n"
req headers: {
  "Content-Type": "application/octet-stream",
  "Content-Range": "bytes 0-2097151/10485760"
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\nrange: bytes=0-2097151\r\n"
req headers: {
  "Content-Type": "application/octet-stream",
  "Content-Range": "bytes 2097152-4194303/10485760"
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\nrange: bytes=2097152-4194303\r\n"
req headers: {
  "Content-Type": "application/octet-stream",
  "Content-Range": "bytes 4194304-6291455/10485760"
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\nrange: bytes=4194304-6291455\r\n"
req headers: {
  "Content-Type": "application/octet-stream",
  "Content-Range": "bytes 6291456-8388607/10485760"
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\nrange: bytes=6291456-8388607\r\n"
req headers: {
  "Content-Type": "application/octet-stream",
  "Content-Range": "bytes 8388608-10485759/10485760"
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 100,
  "remaining": 0,
  "response": 1274308,
  "responseStatus": 200,
  "size": 10485760,
  "status": "complete",
  "uploadId": "182e259e",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=4180095c-a37e-499e-9520-f32a0f101874"
}

*Another difference in my server response when using my own uploader class is that the response is the range written for that particular request i.e. "range: bytes=6291456-8388607" rather than "range: bytes=0-8388607" ("all that has been written") with the default class, but I can make it work either way - and the issue persists in both scenarios.

from ngx-uploadx.

kukhariev avatar kukhariev commented on June 21, 2024

Yeah, it'll force the progress events:

async sendFileContent(): Promise<number> {
//  ...
    const _ = await this.request({
      method: 'PUT',
      body,
      url: this.url,
      headers,
      progress: true // <---  force progress events
    });

    console.log(this._xhr.upload.onprogress);  //  should never be null

// ...
}

from ngx-uploadx.

Spiral1401 avatar Spiral1401 commented on June 21, 2024

Unfortunately I'm not seeing a difference. Here's my updated sendFileContent():

async sendFileContent(): Promise<number> {
    const end = this.chunkSize
      ? Math.min(this.offset + this.chunkSize, this.size)
      : this.size;
    const body = this.file.slice(this.offset, end);
    const headers = {
      'Content-Type': 'application/octet-stream',
      'Content-Range': `bytes ${this.offset}-${end - 1}/${this.size}`
    };
    const blobLength = body.size;
    const reqObjForPrint = {
      method: 'PUT',
      bodyLen: blobLength,
      url: this.url,
      headers,
      progress: true
    };
    const _ = await this.request({
      method: 'PUT',
      body,
      url: this.url,
      headers,
      progress: true
    });
    console.log('req object: ' + JSON.stringify(reqObjForPrint, null, 2));
    return this.getOffsetFromResponse();
  }

(I printed out a slightly different object to avoid console.logging the entire body contents)

And the resulting logs:

{
  "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": ""
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\n"
req object: {
  "method": "PUT",
  "bodyLen": 2097152,
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=ad0ef69b-ec44-4009-8276-76116da78b59",
  "headers": {
    "Content-Type": "application/octet-stream",
    "Content-Range": "bytes 0-2097151/10485760"
  },
  "progress": true
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\nrange: bytes=0-2097151\r\n"
req object: {
  "method": "PUT",
  "bodyLen": 2097152,
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=ad0ef69b-ec44-4009-8276-76116da78b59",
  "headers": {
    "Content-Type": "application/octet-stream",
    "Content-Range": "bytes 2097152-4194303/10485760"
  },
  "progress": true
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\nrange: bytes=2097152-4194303\r\n"
req object: {
  "method": "PUT",
  "bodyLen": 2097152,
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=ad0ef69b-ec44-4009-8276-76116da78b59",
  "headers": {
    "Content-Type": "application/octet-stream",
    "Content-Range": "bytes 4194304-6291455/10485760"
  },
  "progress": true
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\nrange: bytes=4194304-6291455\r\n"
req object: {
  "method": "PUT",
  "bodyLen": 2097152,
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=ad0ef69b-ec44-4009-8276-76116da78b59",
  "headers": {
    "Content-Type": "application/octet-stream",
    "Content-Range": "bytes 6291456-8388607/10485760"
  },
  "progress": true
}
resp headers: "content-length: 7\r\ncontent-type: application/json; charset=utf-8\r\nrange: bytes=6291456-8388607\r\n"
req object: {
  "method": "PUT",
  "bodyLen": 2097152,
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=ad0ef69b-ec44-4009-8276-76116da78b59",
  "headers": {
    "Content-Type": "application/octet-stream",
    "Content-Range": "bytes 8388608-10485759/10485760"
  },
  "progress": true
}
{
  "file": {},
  "name": "dummy2.txt",
  "progress": 100,
  "remaining": 0,
  "response": 1274309,
  "responseStatus": 200,
  "size": 10485760,
  "status": "complete",
  "uploadId": "182e259e",
  "url": "https://localhost:44356/api/v1/document/docFileUpload?tokenId=ad0ef69b-ec44-4009-8276-76116da78b59"
}

from ngx-uploadx.

kukhariev avatar kukhariev commented on June 21, 2024

You need at least v3.3.1 or copy request method from uploader.ts to UploaderXCustom.ts.
v3.3.1 fixes angular aot instanceof bug.

from ngx-uploadx.

Spiral1401 avatar Spiral1401 commented on June 21, 2024

I am using ngx-uploadx 3.3.3. I mention in the original post that I started with 3.1.4 but I already upgraded to latest in an attempt to fix the issue.

Tried copying the request method from ngx-uploadx source into my UploaderXCustom class anyway but things in the base Uploader class being private (onProgress(), startTime, stateChange() ) are making it tricky to do without copying several other things into my own class.

Have to switch gears on my end for a few hours at least but I'll jump back on this soon. Appreciate your help.

from ngx-uploadx.

Spiral1401 avatar Spiral1401 commented on June 21, 2024

Actually, I think I just found the issue. It fits too perfectly. And I only just started using the service worker semi recently in this particular app, so it lines up time-wise as well.
angular/angular#24683

ng serve does not use the angular service worker, while all the other methods of running the app I am using do. So... this has to be it. And I imagine, there is nothing you can do to fix it - but it looks like I can modify my service worker configuration to allow it to work.

I'll try to drop some further info in here if I get the fix working (might help with documenting it for other people).

from ngx-uploadx.

Related Issues (20)

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.