To use Google Drive you must pass Google's Tier 2 Security Assessment

Thanks I’m glad to finally have something that’s working :slight_smile:

Yup, the transfer is taking place server side. The token and Drive meta data get posted to my server which does the downloading and transferring. However, to get thumbnails to show in the Uppy Dashboard I did do a client side request to get them (“document” being what the Picker API provides):

// get thumbnail links for all selected files
const requests = documents.map(document => {
  return fetch(`https://www.googleapis.com/drive/v3/files/${document.id}?fields=thumbnailLink,id`, {
    headers: {
      Authorization: `Bearer ${_accessToken}`
    }
  })
  .then(response => response.json())
})
const responses = await Promise.all(requests)

Then, converting the documents to Uppy files:

documents.forEach(document => {
  uppy.addFile({
      name: document.name,
      type: document.mimeType,
      data: {}, // file blob
      meta: {
        // optional, store the directory path of a file so Uppy can tell identical files in different directories apart.
        relativePath: document.id
      },
      source: 'GooglePicker', // optional, determines the source of the file, for example, Instagram.
      isRemote: true, // when true, will use the preview field to show a thumbnail in dashboard
      preview: document.thumbnailLink, // from `responses`, but added to the document
      remote: document
    });
  })

I ended up writing a custom uploader to handle the documents from Picker. The uploader gets swapped out using Uppy’s onBeforeUpload hook (Though I guess the plugin could just ignore files it doesn’t care about).

onBeforeUpload(files) { // if necessary, change which uploader to use
    console.log('onBeforeUpload')

    // check which uploader needs to be used
    files = Object.values(files)
    const file = files[0]
    let requiredUploaderType
    if (file.source === 'GooglePicker') {
      requiredUploaderType = 'GooglePickerUploader'
    } else if (file.isRemote) {
      requiredUploaderType = 'RemoteUploader'
    } else if (!file.isRemote) {
      requiredUploaderType = 'LocalUploader'
    }

    // remove previous uploader
    const uploader = uppy.getPlugin('uploader')
    if (uploader && uploader.type !== requiredUploaderType) {
      uppy.removePlugin(uploader)
    } else if (uploader && uploader.type === requiredUploaderType) {
      // update folder in case user opened a different one since previous upload
      uploader.setOptions({
        folderId: getActiveFolderID()
      })
      return true
    }

    // select uploader based on the selected files
    if (requiredUploaderType === 'GooglePickerUploader') {
      console.log('use googlepicker uploader instead')
      uppy.use(GooglePickerUploader, {
        id: 'uploader',
        authToken: accessToken(),
        folderId: getActiveFolderID()
      })
    } else if (requiredUploaderType === 'RemoteUploader') {
      console.log('use remote uploader')
      uppy.use(RemoteUploader, {
        id: 'uploader',
        accountId,
        companionUrl,
        folderId: getActiveFolderID()
      })
    } else if (requiredUploaderType === 'LocalUploader') {
      console.log('use local uploader')
      uppy.use(LocalUploader, {
        id: 'uploader',
        folderId: getActiveFolderID()
      })
    }

    return true
  }

And here’s the plugin for reference:

import Uppy from '../../../../../serve/vendor/uppy/dist/uppy.js'

/**
 * An Uppy plugin to upload files from Google Picker. Files are selected using
 * the Google Picker API to then be uploaded to our CDN (via the asset controller).
 *
 * This class is only responsible for taking the files picked and passing them to
 * the asset controller.
 */
export default class GooglePickerUploader extends Uppy.BasePlugin {
  constructor (uppy, opts) {
    super(uppy, opts)
    this.id = opts.id || 'GooglePickerUploader'
    this.type = 'GooglePickerUploader'
    this.authToken = opts.authToken
    this.folderId = opts.folderId
  }

  install () {
    this.uppy.addPreProcessor(this.prepareUpload)
    this.uppy.addUploader(this.upload)
    console.log('PickerUploader install')
  }

  uninstall () {
    this.uppy.removePreProcessor(this.prepareUpload)
    this.uppy.removeUploader(this.upload)
    console.log('PickerUploader uninstall')
  }

  // recommended way to define functions for hooks: https://uppy.io/docs/guides/building-plugins/#upload-hooks
  prepareUpload = (fileIDs) => {
    console.log(this) // `this` refers to the `MyPlugin` instance.
    return Promise.resolve()
  }

  upload = (ids) => {
    console.log('google picker upload', ids.length)
    const files = ids.map(id => this.uppy.getFile(id))

    const uploads = files.map(file => this.uploadDocument(file))
    return Promise.all(uploads)
  }

  uploadDocument (uppyFile) {
    this.uppy.setFileState(uppyFile.id, {
      progress: {
        uploadStarted: Date.now(),
      }
    })

    return new Promise((resolve, reject) => {
      const form = new FormData()
      form.set('oauthToken', this.authToken)
      form.set('documents', JSON.stringify([uppyFile.remote]))
      form.set('folderId', Number.parseInt(this.folderId))
      fetch('/asset/uploadGoogleDriveFile', { method: 'POST', body: form })
        .then(response => response.json())
        .then(responseText => {
          console.log(responseText)
          if (responseText.error) {
            this.uppy.setFileState(uppyFile.id, {
              error: responseText.error.message
            })
            this.uppy.info(`${uppyFile.name} failed to upload: ${responseText.error.message}`, 'error')
            reject()
          } else {
            this.uppy.setFileState(uppyFile.id, {
              progress: {
                bytesUploaded: uppyFile.size,
                percentage: 100,
                uploadComplete: true,
              }
            })

            // update total/overall upload progress
            const filesCompleted = this.uppy.getFiles().filter(file => file.progress.uploadComplete).length
            const numFiles = this.uppy.getFiles().length
            this.uppy.setState({
              totalProgress: Math.round(filesCompleted / numFiles * 100)
            })

            resolve()
          }
        })
        .catch(error => {
          console.error(error)
        })
    })
  }
}

Hopefully this helps. Best of luck!

1 Like