ky icon indicating copy to clipboard operation
ky copied to clipboard

adding onUploadProgress makes the api failed

Open nhridoy opened this issue 5 months ago • 9 comments

I am trying to upload to s3 using pre-signed url. Here is the code for that:

const handleImageChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    try {
      // Get presigned URL from the server
      const file = e.target.files?.[0];

      if (!file) {
        toast.error("No file selected");
        return;
      }
      const formData = new FormData();

      formData.append("file_name", file.name);
      formData.append("file_type", file.type);
      formData.append("file_size", file.size.toString());

      // Get presigned URL from the server
      const presignedUrlResponse = await apiClient<PresignedUrlType>(
        `/api/proxy/options/generate-presigned-url`,
        { method: "post", body: formData }
      );

      if (presignedUrlResponse.status !== 200) {
        toast.error("Failed to get presigned URL");
        return;
      }

      const presignedUrlData = await presignedUrlResponse.json();
      const presignedUrl = presignedUrlData.data.presigned_url;

      // Upload the file to the presigned URL
      const uploadResponse = await ky.put(presignedUrl, {
        body: file,
      });

    } catch (error) {
      console.error((error as Error).message);
    }
  };

And as expected it works fine. But when I am adding onUploadProgress it is failing to upload:

const handleImageChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    try {
      // Get presigned URL from the server
      const file = e.target.files?.[0];

      if (!file) {
        toast.error("No file selected");
        return;
      }
      const formData = new FormData();

      formData.append("file_name", file.name);
      formData.append("file_type", file.type);
      formData.append("file_size", file.size.toString());

      // Get presigned URL from the server
      const presignedUrlResponse = await apiClient<PresignedUrlType>(
        `/api/proxy/options/generate-presigned-url`,
        { method: "post", body: formData }
      );

      if (presignedUrlResponse.status !== 200) {
        toast.error("Failed to get presigned URL");
        return;
      }

      const presignedUrlData = await presignedUrlResponse.json();
      const presignedUrl = presignedUrlData.data.presigned_url;

      // Upload the file to the presigned URL
      const uploadResponse = await ky.put(presignedUrl, {
        body: file,
        onUploadProgress: (progress, chunk) => {
          // Example output:
          // `0% - 0 of 1271 bytes`
          // `100% - 1271 of 1271 bytes`
          console.log(
            `${progress.percent * 100}% - ${progress.transferredBytes} of ${
              progress.totalBytes
            } bytes`
          );
        },
      });

    } catch (error) {
      console.error((error as Error).message);
    }
  };

Here is the full log:

99% - 30627 of 30627 bytes
100% - 30627 of 30627 bytes

QRCodeForm.tsx:147   PUT https://potential-dev2.s3.amazonaws.com/e4p_dev/private/dir_20250916_011143_0873/qr%20%281%29.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAU6GDX3LAHVYE7YBU%2F20250915%2Fap-northeast-2%2Fs3%2Faws4_request&X-Amz-Date=20250915T161143Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=700c264680156ba2466f4e7484f5423700626b114d7ef37e8f5911e630000bc7 net::ERR_ALPN_NEGOTIATION_FAILED
(anonymous) @ timeout.ts:25
timeout @ timeout.ts:15
_fetch @ Ky.ts:338
function_ @ Ky.ts:42
await in function_
_retry @ Ky.ts:285
create @ Ky.ts:87
ky.<computed> @ index.ts:16
handleImageChange @ QRCodeForm.tsx:147
await in handleImageChange
executeDispatch @ react-dom-client.development.js:21336
runWithFiberInDEV @ react-dom-client.development.js:996
processDispatchQueue @ react-dom-client.development.js:21386
(anonymous) @ react-dom-client.development.js:21985
batchedUpdates$1 @ react-dom-client.development.js:3334
dispatchEventForPluginEventSystem @ react-dom-client.development.js:21540
dispatchEvent @ react-dom-client.development.js:26873
dispatchDiscreteEvent @ react-dom-client.development.js:26841
QRCodeForm.tsx:147   PUT https://potential-dev2.s3.amazonaws.com/e4p_dev/private/dir_20250916_011143_0873/qr%20%281%29.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAU6GDX3LAHVYE7YBU%2F20250915%2Fap-northeast-2%2Fs3%2Faws4_request&X-Amz-Date=20250915T161143Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=700c264680156ba2466f4e7484f5423700626b114d7ef37e8f5911e630000bc7 net::ERR_ALPN_NEGOTIATION_FAILED
(anonymous) @ timeout.ts:25
timeout @ timeout.ts:15
_fetch @ Ky.ts:338
function_ @ Ky.ts:42
await in function_
_retry @ Ky.ts:285
_retry @ Ky.ts:309
await in _retry
create @ Ky.ts:87
ky.<computed> @ index.ts:16
handleImageChange @ QRCodeForm.tsx:147
await in handleImageChange
executeDispatch @ react-dom-client.development.js:21336
runWithFiberInDEV @ react-dom-client.development.js:996
processDispatchQueue @ react-dom-client.development.js:21386
(anonymous) @ react-dom-client.development.js:21985
batchedUpdates$1 @ react-dom-client.development.js:3334
dispatchEventForPluginEventSystem @ react-dom-client.development.js:21540
dispatchEvent @ react-dom-client.development.js:26873
dispatchDiscreteEvent @ react-dom-client.development.js:26841
QRCodeForm.tsx:147   PUT https://potential-dev2.s3.amazonaws.com/e4p_dev/private/dir_20250916_011143_0873/qr%20%281%29.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAU6GDX3LAHVYE7YBU%2F20250915%2Fap-northeast-2%2Fs3%2Faws4_request&X-Amz-Date=20250915T161143Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=700c264680156ba2466f4e7484f5423700626b114d7ef37e8f5911e630000bc7 net::ERR_ALPN_NEGOTIATION_FAILED

Also the upload progress it is logging is fake:

99% - 30627 of 30627 bytes
100% - 30627 of 30627 bytes

nhridoy avatar Sep 15 '25 16:09 nhridoy

Can you use the network tab to show us the request headers, both with and without onUploadProgress?

sholladay avatar Sep 15 '25 17:09 sholladay

Can you use the network tab to show us the request headers, both with and without onUploadProgress?

Without onUploadProgress:

Image

With onUploadProgress:

Image

Here it is @sholladay

nhridoy avatar Sep 15 '25 17:09 nhridoy

Thanks, looks like a bug to me. If you can find a way to make a minimal reproducible example that I could run on my machine, that would be useful.

I am working on re-writing the uploadProgress logic right now, so it would be good to see if my changes fix this or not.

sholladay avatar Sep 15 '25 17:09 sholladay

Thanks, looks like a bug to me. If you can find a way to make a minimal reproducible example that I could run on my machine, that would be useful.

I am working on re-writing the uploadProgress logic right now, so it would be good to see if my changes fix this or not.

I've made a minimal reproducible example here. Clone this repo and checkout the ky-upload branch, then run node ./server.js start the service.

Hope it helps.

Chrysochrome avatar Oct 27 '25 05:10 Chrysochrome

Same here, we get an ERR_ALPN_NEGOTIATION_FAILED error when using with onUploadProgress to post form data to a Next.js API route.

techpoint24de avatar Oct 29 '25 13:10 techpoint24de

Same here, we get an ERR_ALPN_NEGOTIATION_FAILED error when using with onUploadProgress to post form data to a Next.js API route.

https://github.com/sindresorhus/ky/issues/733#issuecomment-3276676330

bwhyman avatar Oct 29 '25 15:10 bwhyman

Ky v1.11.0 includes a rewrite of the progress callbacks and resolves a number of small bugs and inconsistencies. I haven't investigated this particular issue yet, but the update is worth a try if you haven't already.

sholladay avatar Oct 29 '25 20:10 sholladay

Ky v1.11.0 includes a rewrite of the progress callbacks and resolves a number of small bugs and inconsistencies. I haven't investigated this particular issue yet, but the update is worth a try if you haven't already.

I tried Ky v1.13.0, but it failed again. I can reproduce in Microsoft Edge 141.0.3537.99 and Google Chrome 144.0.7501.2

Chrysochrome avatar Oct 30 '25 06:10 Chrysochrome

Testing Env: ky 1.13.0; Vue 3.5.22; chrome: 141 Expection: POST http://localhost:5173/api/uploadtest net::ERR_ALPN_NEGOTIATION_FAILED

// code is simple const api = ky.extend({ prefixUrl: '/api/', retry: 0, }) export async function upload(url: string, data: FormData) { await api.post(url, { body: data, // working without onUploadProgress onUploadProgress: (progress) => { console.log('onUploadProgress') }, }) }

without onUploadProgress, Request headers

Image with onUploadProgress Image

bwhyman avatar Oct 30 '25 07:10 bwhyman