vercel / storage

Vercel Postgres, KV, Blob, and Edge Config
https://vercel.com/storage
Apache License 2.0
517 stars 57 forks source link

@vercel/blob - not possible to use `client` lib in node.js env #638

Closed pie6k closed 7 months ago

pie6k commented 8 months ago

Use case:

I want to upload files larger than 4.5MB (vercel limit) from node.js enviroment (to be specific - from Electron main process which runs in node env)

I did follow steps at https://vercel.com/docs/storage/vercel-blob/client-upload and it works perfectly fine, but I'm only able to run it from browser env.

If I run it in node env I hit ${methodName} must be called from a client environment error. (https://github.com/vercel/storage/blob/5fb6969801107030adbdd156a1a1f2da8414eaad/packages/blob/src/client.ts#L52)

I did check the file https://github.com/vercel/storage/blob/5fb6969801107030adbdd156a1a1f2da8414eaad/packages/blob/src/client.ts and it seems to barely be using window, the only real case is

function toAbsoluteUrl(url: string): string {
  return new URL(url, window.location.href).href;
}

I think it might be quite common use case to handle such uploads externally. Is there any particular reason this use-case is restricted to Browser env?

vvo commented 8 months ago

@pie6k Sorry you're hitting this limitation, I am glad you're willing to upload large files from Node.js directly.

To solve your issue, you can use the normal put method, I'll explain why right after:

// use multipart is the upload is large (> 10MB for example)
const blob = await put(filename, content, { access: 'public', multipart: true });

A few notes:

  1. The 4.5 MB limitation you're talking about is only relative to when you deploy a website to Vercel, and you have a route that accepts files. Then this route can only accept files up to 4.5 MB. In this case you want to use client uploads yes.
  2. We will probably get rid of this window check, this is where most of the libraries are going nowadays (client and server definitions are more and more blurry, so anything can be client or server)
  3. If you really want to use client uploads from your Electron app, perhaps because you don't want to store your Blob read write token in the app when distributing it, then you can still do it this way:
import { generateClientTokenFromReadWriteToken } from '@vercel/blob/client';
import { put } from '@vercel/blob';

const clientToken = await client.generateClientTokenFromReadWriteToken({
  token, // read write token
  pathname: 'file.txt',
  // onUploadCompleted: {
    // callbackUrl: 'https://yourwebsite.com/upload-complete',
  // },
});

const blob = await put('file.txt', content, { access: 'public', multipart: 'true', token: clientToken });

Let me know if this makes sense to you now and if you think we should make more changes to documentation or API. Thanks!

pie6k commented 8 months ago

Ok, indeed I just passed this token into options of put. Works like a charm! Thanks a lot!

Nit: would be lovely to be able to track upload progress (currently I simulate it assuming 1Mbps upload speed 👾)

sajeeIfonix commented 7 months ago
clientToken

@pie6k Sorry you're hitting this limitation, I am glad you're willing to upload large files from Node.js directly.

To solve your issue, you can use the normal put method, I'll explain why right after:

// use multipart is the upload is large (> 10MB for example)
const blob = await put(filename, content, { access: 'public', multipart: true });

A few notes:

  1. The 4.5 MB limitation you're talking about is only relative to when you deploy a website to Vercel, and you have a route that accepts files. Then this route can only accept files up to 4.5 MB. In this case you want to use client uploads yes.
  2. We will probably get rid of this window check, this is where most of the libraries are going nowadays (client and server definitions are more and more blurry, so anything can be client or server)
  3. If you really want to use client uploads from your Electron app, perhaps because you don't want to store your Blob read write token in the app when distributing it, then you can still do it this way:
import { generateClientTokenFromReadWriteToken } from '@vercel/blob/client';
import { put } from '@vercel/blob';

const clientToken = await client.generateClientTokenFromReadWriteToken({
  token, // read write token
  pathname: 'file.txt',
  // onUploadCompleted: {
    // callbackUrl: 'https://yourwebsite.com/upload-complete',
  // },
});

const blob = await put('file.txt', content, { access: 'public', multipart: 'true' });

Let me know if this makes sense to you now and if you think we should make more changes to documentation or API. Thanks!

@vvo Where is the clientToken used?

vvo commented 7 months ago

@sajeeIfonix I forgot to use it in the example, now fixed, here it is updated:

import { generateClientTokenFromReadWriteToken } from '@vercel/blob/client';
import { put } from '@vercel/blob';

const clientToken = await client.generateClientTokenFromReadWriteToken({
  token, // read write token
  pathname: 'file.txt',
  // onUploadCompleted: {
    // callbackUrl: 'https://yourwebsite.com/upload-complete',
  // },
});

const blob = await put('file.txt', content, { access: 'public', multipart: 'true', token: clientToken });

Hope this helps. BTW what's your usecase for client uploads from server? Thanks!

sajeeIfonix commented 7 months ago

@sajeeIfonix I forgot to use it in the example, now fixed, here it is updated:

import { generateClientTokenFromReadWriteToken } from '@vercel/blob/client';
import { put } from '@vercel/blob';

const clientToken = await client.generateClientTokenFromReadWriteToken({
  token, // read write token
  pathname: 'file.txt',
  // onUploadCompleted: {
    // callbackUrl: 'https://yourwebsite.com/upload-complete',
  // },
});

const blob = await put('file.txt', content, { access: 'public', multipart: 'true', token: clientToken });

Hope this helps. BTW what's your usecase for client uploads from server? Thanks!

Hello @vvo ,

Thanks for the clarification.

I have a react native mobile application. I want to upload images and PDFs from the mobile app to Vercel blob. What I understand from your code snippet is,

  1. I need to request for the READ-WRITE token from the server.
  2. Then I need to use your code snippet to upload the file to Vercel Blob.

Have I understood the task correctly?

vvo commented 7 months ago

I have a react native mobile application

Can you create a new issue then with more details? Ideally on React Native you can just use https://vercel.com/docs/storage/vercel-blob/client-upload directly without any issue, can you try that?

vvo commented 7 months ago

I need to request for the READ-WRITE token from the server.

Ideally no, your VERCEL_BLOB_READ_WRITE_TOKEN is sensitive and thus should never be sent to a client (react native, electron app, browser...), instead use client uploads either with our built-in upload() and handleUpload() methods or manually by creating an endpoint on your side that generate client tokens and send them to clients (react native) to then be used in a normal put()

sajeeIfonix commented 7 months ago

I have a react native mobile application

Can you create a new issue then with more details? Ideally on React Native you can just use https://vercel.com/docs/storage/vercel-blob/client-upload directly without any issue, can you try that?

I tried to do this previously. However, i am facing this GutHub issue https://github.com/vercel/storage/issues/505. So i was looking at your snippet for a work around.

mmkal commented 3 months ago

I think the window check should just be removed. It works fine if you do this hack before using upload(...) (in nodejs, likely an equivalent way to set a global variable would work in React Native/Electron etc.):

Object.assign{global, {window: new URL('https://mywebsite.com')})

Which suggests the check is only for its own sake- there's nothing else that depends on browser-specific APIs. And removing it would allow client uploading from various clients which aren't browsers (React Native, Electron, CLIs, GitHub Actions, etc. etc.) - without having to build a custom flow and risk confusing people into putting their read-write token somewhere unsafe. Users will be able to just follow the docs for client uploads.