transloadit / uppy

The next open source file uploader for web browsers :dog:

Home Page:https://uppy.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support async upload parameters on XHR upload

lmyslinski opened this issue · comments

Initial checklist

  • I understand this is a feature request and questions should be posted in the Community Forum
  • I searched issues and couldn’t find anything (or linked relevant results below)

Problem

Currently, we can only pass in HTTP headers for XHR upload when creating an Uppy instance. This is a problem when the auth token is short-lived - in my case, the token is only valid for 60 seconds, however our clients often can keep the tab open for way longer than that. Currently I have a very ugly solution with periodically running an effect hook for recreating the Uppy instance:

  const { getToken } = useAuth(); // async function for getting a new auth token value

  const [token, setToken] = useState<string>(authToken);
  const updateToken = async () => {
    const res = await getToken();
    if (res != null && res != token) {
      setToken(res);
    }
  };
  useEffect(() => {
    const timer = setInterval(updateToken, 30000);
    return () => clearInterval(timer);
  }, []);

  useEffect(() => {
    const newUppy = getUppy(token);
    setUppy(newUppy);
  }, [token]);

  const getUppy = (newToken: string) => {
    return new Uppy(uppyConfig).use(XHR, {
      endpoint: getUploadUrl(),
      withCredentials: true,
      headers: {
        Authorization: `Bearer ${newToken}`,
      },
    });
  };

  const [uppy, setUppy] = useState<Uppy>(getUppy(authToken));

Solution

Add an async getUploadParameters on XHR upload, similar to what S3 plugin is doing

Alternatives

Open for alternatives but can't think of anything other than making the above code slightly nicer with some auto revalidator instead of a useEffect hook

Hi, you can also do this and not destroy the instance:

  useEffect(() => {
    uppy.getPlugin('XHRUpload').setOptions({
      headers: {
        Authorization: `Bearer ${token}`,
      }
    })
  }, [token]);

The problem then is that it isn't retried. With tus you can do this:

import Uppy from '@uppy/core';
import Tus from '@uppy/tus';

new Uppy().use(Tus, {
  endpoint: '',
  async onBeforeRequest(req) {
    const token = await getAuthToken();
    req.setHeader('Authorization', `Bearer ${token}`);
  },
  onShouldRetry(err, retryAttempt, options, next) {
    if (err?.originalResponse?.getStatus() === 401) {
      return true;
    }
    return next(err);
  },
  async onAfterResponse(req, res) {
    if (res.getStatus() === 401) {
      await refreshAuthToken();
    }
  },
});

Ideally this would be consistent between uploaders.

yeah onBeforeRequest is exactly what I had in mind. I'll try this one out for now, thanks

Closing this in favor of the mentioned issue.

I just saw this in the codebase:

queuedRequest = this.requests.run(() => {
// When using an authentication system like JWT, the bearer token goes as a header. This
// header needs to be fresh each time the token is refreshed so computing and setting the
// headers just before the upload starts enables this kind of authentication to work properly.
// Otherwise, half-way through the list of uploads the token could be stale and the upload would fail.
const currentOpts = this.getOptions(file)
Object.keys(currentOpts.headers).forEach((header) => {
xhr.setRequestHeader(header, currentOpts.headers[header])
})

So my useEffect suggestion in #5042 (comment) should be sufficient for now.