Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@uppy/companion: implement refresh for authentication tokens #4448

Merged
merged 23 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 5 additions & 33 deletions packages/@uppy/aws-s3-multipart/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import BasePlugin from '@uppy/core/lib/BasePlugin.js'
import UploaderPlugin from '@uppy/core/lib/UploaderPlugin.js'
import { Socket, Provider, RequestClient } from '@uppy/companion-client'
import EventManager from '@uppy/utils/lib/EventManager'
import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress'
Expand Down Expand Up @@ -303,11 +303,9 @@ class HTTPCommunicationQueue {
}
}

export default class AwsS3Multipart extends BasePlugin {
export default class AwsS3Multipart extends UploaderPlugin {
static VERSION = packageJson.version

#queueRequestSocketToken

#companionCommunicationQueue

#client
Expand Down Expand Up @@ -359,7 +357,7 @@ export default class AwsS3Multipart extends BasePlugin {
this.uploaderEvents = Object.create(null)
this.uploaderSockets = Object.create(null)

this.#queueRequestSocketToken = this.requests.wrapPromiseFunction(this.#requestSocketToken, { priority: -1 })
this.setQueueRequestSocketToken(this.requests.wrapPromiseFunction(this.#requestSocketToken, { priority: -1 }))
}

[Symbol.for('uppy test: getClient')] () { return this.#client }
Expand Down Expand Up @@ -683,32 +681,6 @@ export default class AwsS3Multipart extends BasePlugin {
return res.token
}

// NOTE! Keep this duplicated code in sync with other plugins
// TODO we should probably abstract this into a common function
async #uploadRemote (file, options) {
this.resetUploaderReferences(file.id)

try {
if (file.serverToken) {
return await this.connectToServerSocket(file)
}
const serverToken = await this.#queueRequestSocketToken(file, options).abortOn(options?.signal)

if (!this.uppy.getState().files[file.id]) return undefined

this.uppy.setFileState(file.id, { serverToken })
return await this.connectToServerSocket(this.uppy.getFile(file.id))
} catch (err) {
if (err?.cause?.name !== 'AbortError') {
this.uppy.setFileState(file.id, { serverToken: undefined })
this.uppy.emit('upload-error', file, err)
throw err
}
// The file upload was aborted, it’s not an error
return undefined
}
}

async connectToServerSocket (file) {
return new Promise((resolve, reject) => {
let queuedRequest
Expand Down Expand Up @@ -840,10 +812,10 @@ export default class AwsS3Multipart extends BasePlugin {
const removedHandler = (removedFile) => {
if (removedFile.id === file.id) controller.abort()
}

this.uppy.on('file-removed', removedHandler)

const uploadPromise = this.#uploadRemote(file, { signal: controller.signal })
this.resetUploaderReferences(file.id)
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
const uploadPromise = this.uploadRemoteFile(file, { signal: controller.signal })

this.requests.wrapSyncFunction(() => {
this.uppy.off('file-removed', removedHandler)
Expand Down
92 changes: 5 additions & 87 deletions packages/@uppy/aws-s3/src/MiniXHRUpload.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { nanoid } from 'nanoid/non-secure'
import { Provider, RequestClient, Socket } from '@uppy/companion-client'
import { Socket } from '@uppy/companion-client'
import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress'
import getSocketHost from '@uppy/utils/lib/getSocketHost'
import EventManager from '@uppy/utils/lib/EventManager'
Expand Down Expand Up @@ -53,8 +53,6 @@ function createFormDataUpload (file, opts) {
const createBareUpload = file => file.data

export default class MiniXHRUpload {
#queueRequestSocketToken

constructor (uppy, opts) {
this.uppy = uppy
this.opts = {
Expand All @@ -67,11 +65,9 @@ export default class MiniXHRUpload {
this.requests = opts[internalRateLimitedQueue]
this.uploaderEvents = Object.create(null)
this.i18n = opts.i18n

this.#queueRequestSocketToken = this.requests.wrapPromiseFunction(this.#requestSocketToken, { priority: -1 })
}

#getOptions (file) {
getOptions (file) {
const { uppy } = this

const overrides = uppy.getState().xhrUpload
Expand All @@ -89,31 +85,6 @@ export default class MiniXHRUpload {
return opts
}

uploadFile (id, current, total) {
const file = this.uppy.getFile(id)
this.uppy.log(`uploading ${current} of ${total}`)

if (file.error) {
throw new Error(file.error)
} else if (file.isRemote) {
const controller = new AbortController()

const removedHandler = (removedFile) => {
if (removedFile.id === file.id) controller.abort()
}
this.uppy.on('file-removed', removedHandler)

const uploadPromise = this.#uploadRemoteFile(file, { signal: controller.signal })

this.requests.wrapSyncFunction(() => {
this.uppy.off('file-removed', removedHandler)
}, { priority: -1 })()

return uploadPromise
}
return this.#uploadLocalFile(file, current, total)
}

#addEventHandlerForFile (eventName, fileID, eventHandler) {
this.uploaderEvents[fileID].on(eventName, (fileOrID) => {
// TODO (major): refactor Uppy events to consistently send file objects (or consistently IDs)
Expand All @@ -130,8 +101,8 @@ export default class MiniXHRUpload {
})
}

#uploadLocalFile (file) {
const opts = this.#getOptions(file)
uploadLocalFile (file) {
const opts = this.getOptions(file)

return new Promise((resolve, reject) => {
// This is done in index.js in the S3 plugin.
Expand Down Expand Up @@ -265,62 +236,9 @@ export default class MiniXHRUpload {
})
}

#requestSocketToken = async (file) => {
const opts = this.#getOptions(file)
const Client = file.remote.providerOptions.provider ? Provider : RequestClient
const client = new Client(this.uppy, file.remote.providerOptions)
const allowedMetaFields = Array.isArray(opts.allowedMetaFields)
? opts.allowedMetaFields
// Send along all fields by default.
: Object.keys(file.meta)

if (file.tus) {
// Install file-specific upload overrides.
Object.assign(opts, file.tus)
}

const res = await client.post(file.remote.url, {
...file.remote.body,
protocol: 'multipart',
endpoint: opts.endpoint,
size: file.data.size,
fieldname: opts.fieldName,
metadata: Object.fromEntries(allowedMetaFields.map(name => [name, file.meta[name]])),
httpMethod: opts.method,
useFormData: opts.formData,
headers: opts.headers,
})
return res.token
}

// NOTE! Keep this duplicated code in sync with other plugins
// TODO we should probably abstract this into a common function
async #uploadRemoteFile (file, options) {
// TODO: we could rewrite this to use server-sent events instead of creating WebSockets.
try {
if (file.serverToken) {
return await this.connectToServerSocket(file)
}
const serverToken = await this.#queueRequestSocketToken(file, options).abortOn(options?.signal)

if (!this.uppy.getState().files[file.id]) return undefined

this.uppy.setFileState(file.id, { serverToken })
return await this.connectToServerSocket(this.uppy.getFile(file.id))
} catch (err) {
if (err?.cause?.name !== 'AbortError') {
this.uppy.setFileState(file.id, { serverToken: undefined })
this.uppy.emit('upload-error', file, err)
throw err
}
// The file upload was aborted, it’s not an error
return undefined
}
}

async connectToServerSocket (file) {
return new Promise((resolve, reject) => {
const opts = this.#getOptions(file)
const opts = this.getOptions(file)
const token = file.serverToken
const host = getSocketHost(file.remote.companionUrl)
let socket
Expand Down
68 changes: 64 additions & 4 deletions packages/@uppy/aws-s3/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
* the XHRUpload code, but at least it's not horrifically broken :)
*/

import BasePlugin from '@uppy/core/lib/BasePlugin.js'
import UploaderPlugin from '@uppy/core/lib/UploaderPlugin.js'
import AwsS3Multipart from '@uppy/aws-s3-multipart'
import { RateLimitedQueue, internalRateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue'
import { RequestClient } from '@uppy/companion-client'
import { RequestClient, Provider } from '@uppy/companion-client'
import { filterNonFailedFiles, filterFilesToEmitUploadStarted } from '@uppy/utils/lib/fileFilters'

import packageJson from '../package.json'
Expand Down Expand Up @@ -103,7 +103,7 @@ function defaultGetResponseError (content, xhr) {
let warnedSuccessActionStatus = false

// TODO deprecate this, will use s3-multipart instead
export default class AwsS3 extends BasePlugin {
export default class AwsS3 extends UploaderPlugin {
static VERSION = packageJson.version

#client
Expand Down Expand Up @@ -144,6 +144,8 @@ export default class AwsS3 extends BasePlugin {

this.#client = new RequestClient(uppy, opts)
this.#requests = new RateLimitedQueue(this.opts.limit)

this.setQueueRequestSocketToken(this.#requests.wrapPromiseFunction(this.#requestSocketToken, { priority: -1 }))
}

[Symbol.for('uppy test: getClient')] () { return this.#client }
Expand Down Expand Up @@ -228,7 +230,7 @@ export default class AwsS3 extends BasePlugin {
xhrUpload: xhrOpts,
})

return this.#uploader.uploadFile(file.id, index, numberOfFiles)
return this.uploadFile(file.id, index, numberOfFiles)
}).catch((error) => {
delete paramsPromises[id]

Expand All @@ -247,6 +249,64 @@ export default class AwsS3 extends BasePlugin {
return Promise.resolve()
}

connectToServerSocket (file) {
return this.#uploader.connectToServerSocket(file)
}

#requestSocketToken = async (file) => {
const opts = this.#uploader.getOptions(file)
const Client = file.remote.providerOptions.provider ? Provider : RequestClient
const client = new Client(this.uppy, file.remote.providerOptions)
const allowedMetaFields = Array.isArray(opts.allowedMetaFields)
? opts.allowedMetaFields
// Send along all fields by default.
: Object.keys(file.meta)

if (file.tus) {
// Install file-specific upload overrides.
Object.assign(opts, file.tus)
}

const res = await client.post(file.remote.url, {
...file.remote.body,
protocol: 'multipart',
endpoint: opts.endpoint,
size: file.data.size,
fieldname: opts.fieldName,
metadata: Object.fromEntries(allowedMetaFields.map(name => [name, file.meta[name]])),
httpMethod: opts.method,
useFormData: opts.formData,
headers: opts.headers,
})
return res.token
}

uploadFile (id, current, total) {
const file = this.uppy.getFile(id)
this.uppy.log(`uploading ${current} of ${total}`)

if (file.error) throw new Error(file.error)

if (file.isRemote) {
const controller = new AbortController()

const removedHandler = (removedFile) => {
if (removedFile.id === file.id) controller.abort()
}
this.uppy.on('file-removed', removedHandler)

const uploadPromise = this.uploadRemoteFile(file, { signal: controller.signal })

this.requests.wrapSyncFunction(() => {
this.uppy.off('file-removed', removedHandler)
}, { priority: -1 })()

return uploadPromise
}

return this.#uploader.uploadLocalFile(file, current, total)
}

install () {
const { uppy } = this
uppy.addPreProcessor(this.#setCompanionHeaders)
Expand Down
Loading