@uppy/companion-client
Advanced tools
Comparing version 3.3.0 to 3.4.0
# @uppy/companion-client | ||
## 3.4.0 | ||
Released: 2023-09-05 | ||
Included in: Uppy v3.15.0 | ||
- @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/companion-client,@uppy/core,@uppy/tus,@uppy/utils,@uppy/xhr-upload: Move remote file upload logic into companion-client (Merlijn Vos / #4573) | ||
## 3.3.0 | ||
@@ -4,0 +11,0 @@ |
@@ -32,4 +32,4 @@ 'use strict'; | ||
export default class Provider extends RequestClient { | ||
constructor(uppy, opts) { | ||
super(uppy, opts); | ||
constructor(uppy, opts, getQueue) { | ||
super(uppy, opts, getQueue); | ||
Object.defineProperty(this, _removeAuthToken, { | ||
@@ -36,0 +36,0 @@ value: _removeAuthToken2 |
@@ -9,5 +9,9 @@ 'use strict'; | ||
import ErrorWithCause from '@uppy/utils/lib/ErrorWithCause'; | ||
import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress'; | ||
import getSocketHost from '@uppy/utils/lib/getSocketHost'; | ||
import EventManager from '@uppy/utils/lib/EventManager'; | ||
import AuthError from './AuthError.js'; | ||
import Socket from './Socket.js'; | ||
const packageJson = { | ||
"version": "3.3.0" | ||
"version": "3.4.0" | ||
}; // Remove the trailing slash so we can always safely append /xyz. | ||
@@ -30,3 +34,5 @@ function stripSlash(url) { | ||
errMsg = errData.requestId ? `${errMsg} request-Id: ${errData.requestId}` : errMsg; | ||
} catch {/* if the response contains invalid JSON, let's ignore the error */} | ||
} catch { | ||
/* if the response contains invalid JSON, let's ignore the error */ | ||
} | ||
throw new Error(errMsg); | ||
@@ -39,5 +45,6 @@ } | ||
var _getUrl = /*#__PURE__*/_classPrivateFieldLooseKey("getUrl"); | ||
var _requestSocketToken = /*#__PURE__*/_classPrivateFieldLooseKey("requestSocketToken"); | ||
_Symbol$for = Symbol.for('uppy test: getCompanionHeaders'); | ||
export default class RequestClient { | ||
constructor(uppy, opts) { | ||
constructor(uppy, opts, getQueue) { | ||
Object.defineProperty(this, _getUrl, { | ||
@@ -50,4 +57,18 @@ value: _getUrl2 | ||
}); | ||
Object.defineProperty(this, _requestSocketToken, { | ||
writable: true, | ||
value: async (file, postBody) => { | ||
if (file.remote.url == null) { | ||
throw new Error('Cannot connect to an undefined URL'); | ||
} | ||
const res = await this.post(file.remote.url, { | ||
...file.remote.body, | ||
...postBody | ||
}); | ||
return res.token; | ||
} | ||
}); | ||
this.uppy = uppy; | ||
this.opts = opts; | ||
this.getQueue = getQueue; | ||
this.onReceiveResponse = this.onReceiveResponse.bind(this); | ||
@@ -226,2 +247,150 @@ _classPrivateFieldLooseBase(this, _companionHeaders)[_companionHeaders] = opts == null ? void 0 : opts.companionHeaders; | ||
} | ||
async uploadRemoteFile(file, reqBody, options) { | ||
if (options === void 0) { | ||
options = {}; | ||
} | ||
try { | ||
if (file.serverToken) { | ||
return await this.connectToServerSocket(file, this.getQueue()); | ||
} | ||
const queueRequestSocketToken = this.getQueue().wrapPromiseFunction(_classPrivateFieldLooseBase(this, _requestSocketToken)[_requestSocketToken], { | ||
priority: -1 | ||
}); | ||
const serverToken = await queueRequestSocketToken(file, reqBody).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), this.getQueue()); | ||
} catch (err) { | ||
var _err$cause; | ||
if ((err == null || (_err$cause = err.cause) == null ? void 0 : _err$cause.name) === 'AbortError') { | ||
// The file upload was aborted, it’s not an error | ||
return undefined; | ||
} | ||
this.uppy.setFileState(file.id, { | ||
serverToken: undefined | ||
}); | ||
this.uppy.emit('upload-error', file, err); | ||
throw err; | ||
} | ||
} | ||
/** | ||
* @param {UppyFile} file | ||
*/ | ||
async connectToServerSocket(file, queue) { | ||
return new Promise((resolve, reject) => { | ||
const token = file.serverToken; | ||
const host = getSocketHost(file.remote.companionUrl); | ||
const socket = new Socket({ | ||
target: `${host}/api/${token}`, | ||
autoOpen: false | ||
}); | ||
const eventManager = new EventManager(this.uppy); | ||
let queuedRequest; | ||
eventManager.onFileRemove(file.id, () => { | ||
socket.send('cancel', {}); | ||
queuedRequest.abort(); | ||
resolve(`upload ${file.id} was removed`); | ||
}); | ||
eventManager.onPause(file.id, isPaused => { | ||
if (isPaused) { | ||
// Remove this file from the queue so another file can start in its place. | ||
socket.send('pause', {}); | ||
queuedRequest.abort(); | ||
} else { | ||
// Resuming an upload should be queued, else you could pause and then | ||
// resume a queued upload to make it skip the queue. | ||
queuedRequest.abort(); | ||
queuedRequest = queue.run(() => { | ||
socket.open(); | ||
socket.send('resume', {}); | ||
return () => {}; | ||
}); | ||
} | ||
}); | ||
eventManager.onPauseAll(file.id, () => { | ||
socket.send('pause', {}); | ||
queuedRequest.abort(); | ||
}); | ||
eventManager.onCancelAll(file.id, function (_temp) { | ||
let { | ||
reason | ||
} = _temp === void 0 ? {} : _temp; | ||
if (reason === 'user') { | ||
socket.send('cancel', {}); | ||
queuedRequest.abort(); | ||
} | ||
resolve(`upload ${file.id} was canceled`); | ||
}); | ||
eventManager.onResumeAll(file.id, () => { | ||
queuedRequest.abort(); | ||
if (file.error) { | ||
socket.send('pause', {}); | ||
} | ||
queuedRequest = queue.run(() => { | ||
socket.open(); | ||
socket.send('resume', {}); | ||
return () => {}; | ||
}); | ||
}); | ||
eventManager.onRetry(file.id, () => { | ||
// Only do the retry if the upload is actually in progress; | ||
// else we could try to send these messages when the upload is still queued. | ||
// We may need a better check for this since the socket may also be closed | ||
// for other reasons, like network failures. | ||
if (socket.isOpen) { | ||
socket.send('pause', {}); | ||
socket.send('resume', {}); | ||
} | ||
}); | ||
eventManager.onRetryAll(file.id, () => { | ||
// See the comment in the onRetry() call | ||
if (socket.isOpen) { | ||
socket.send('pause', {}); | ||
socket.send('resume', {}); | ||
} | ||
}); | ||
socket.on('progress', progressData => emitSocketProgress(this, progressData, file)); | ||
socket.on('error', errData => { | ||
const { | ||
message | ||
} = errData.error; | ||
const error = Object.assign(new Error(message), { | ||
cause: errData.error | ||
}); | ||
// If the remote retry optimisation should not be used, | ||
// close the socket—this will tell companion to clear state and delete the file. | ||
if (!this.opts.useFastRemoteRetry) { | ||
// Remove the serverToken so that a new one will be created for the retry. | ||
this.uppy.setFileState(file.id, { | ||
serverToken: null | ||
}); | ||
} else { | ||
socket.close(); | ||
} | ||
this.uppy.emit('upload-error', file, error); | ||
queuedRequest.done(); | ||
reject(error); | ||
}); | ||
socket.on('success', data => { | ||
const uploadResp = { | ||
uploadURL: data.url | ||
}; | ||
this.uppy.emit('upload-success', file, uploadResp); | ||
queuedRequest.done(); | ||
socket.close(); | ||
resolve(); | ||
}); | ||
queuedRequest = queue.run(() => { | ||
if (file.isPaused) { | ||
socket.send('pause', {}); | ||
} else { | ||
socket.open(); | ||
} | ||
return () => {}; | ||
}); | ||
}); | ||
} | ||
} | ||
@@ -228,0 +397,0 @@ function _getUrl2(url) { |
{ | ||
"name": "@uppy/companion-client", | ||
"description": "Client library for communication with Companion. Intended for use in Uppy plugins.", | ||
"version": "3.3.0", | ||
"version": "3.4.0", | ||
"license": "MIT", | ||
@@ -25,3 +25,3 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"@uppy/utils": "^5.4.3", | ||
"@uppy/utils": "^5.5.0", | ||
"namespace-emitter": "^2.0.1" | ||
@@ -28,0 +28,0 @@ }, |
@@ -33,4 +33,4 @@ 'use strict' | ||
constructor (uppy, opts) { | ||
super(uppy, opts) | ||
constructor (uppy, opts, getQueue) { | ||
super(uppy, opts, getQueue) | ||
this.provider = opts.provider | ||
@@ -37,0 +37,0 @@ this.id = this.provider |
@@ -5,3 +5,8 @@ 'use strict' | ||
import ErrorWithCause from '@uppy/utils/lib/ErrorWithCause' | ||
import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress' | ||
import getSocketHost from '@uppy/utils/lib/getSocketHost' | ||
import EventManager from '@uppy/utils/lib/EventManager' | ||
import AuthError from './AuthError.js' | ||
import Socket from './Socket.js' | ||
@@ -29,4 +34,8 @@ import packageJson from '../package.json' | ||
errMsg = errData.message ? `${errMsg} message: ${errData.message}` : errMsg | ||
errMsg = errData.requestId ? `${errMsg} request-Id: ${errData.requestId}` : errMsg | ||
} catch { /* if the response contains invalid JSON, let's ignore the error */ } | ||
errMsg = errData.requestId | ||
? `${errMsg} request-Id: ${errData.requestId}` | ||
: errMsg | ||
} catch { | ||
/* if the response contains invalid JSON, let's ignore the error */ | ||
} | ||
throw new Error(errMsg) | ||
@@ -43,5 +52,6 @@ } | ||
constructor (uppy, opts) { | ||
constructor (uppy, opts, getQueue) { | ||
this.uppy = uppy | ||
this.opts = opts | ||
this.getQueue = getQueue | ||
this.onReceiveResponse = this.onReceiveResponse.bind(this) | ||
@@ -55,3 +65,5 @@ this.#companionHeaders = opts?.companionHeaders | ||
[Symbol.for('uppy test: getCompanionHeaders')] () { return this.#companionHeaders } | ||
[Symbol.for('uppy test: getCompanionHeaders')] () { | ||
return this.#companionHeaders | ||
} | ||
@@ -115,3 +127,7 @@ get hostname () { | ||
const fallbackAllowedHeaders = ['accept', 'content-type', 'uppy-auth-token'] | ||
const fallbackAllowedHeaders = [ | ||
'accept', | ||
'content-type', | ||
'uppy-auth-token', | ||
] | ||
@@ -128,9 +144,16 @@ const promise = (async () => { | ||
this.uppy.log(`[CompanionClient] adding allowed preflight headers to companion cache: ${this.hostname} ${header}`) | ||
this.uppy.log( | ||
`[CompanionClient] adding allowed preflight headers to companion cache: ${this.hostname} ${header}`, | ||
) | ||
const allowedHeaders = header.split(',').map((headerName) => headerName.trim().toLowerCase()) | ||
const allowedHeaders = header | ||
.split(',') | ||
.map((headerName) => headerName.trim().toLowerCase()) | ||
allowedHeadersCache.set(this.hostname, allowedHeaders) | ||
return allowedHeaders | ||
} catch (err) { | ||
this.uppy.log(`[CompanionClient] unable to make preflight request ${err}`, 'warning') | ||
this.uppy.log( | ||
`[CompanionClient] unable to make preflight request ${err}`, | ||
'warning', | ||
) | ||
// If the user gets a network error or similar, we should try preflight | ||
@@ -148,11 +171,18 @@ // again next time, or else we might get incorrect behaviour. | ||
async preflightAndHeaders (path) { | ||
const [allowedHeaders, headers] = await Promise.all([this.preflight(path), this.headers()]) | ||
const [allowedHeaders, headers] = await Promise.all([ | ||
this.preflight(path), | ||
this.headers(), | ||
]) | ||
// filter to keep only allowed Headers | ||
return Object.fromEntries(Object.entries(headers).filter(([header]) => { | ||
if (!allowedHeaders.includes(header.toLowerCase())) { | ||
this.uppy.log(`[CompanionClient] excluding disallowed header ${header}`) | ||
return false | ||
} | ||
return true | ||
})) | ||
return Object.fromEntries( | ||
Object.entries(headers).filter(([header]) => { | ||
if (!allowedHeaders.includes(header.toLowerCase())) { | ||
this.uppy.log( | ||
`[CompanionClient] excluding disallowed header ${header}`, | ||
) | ||
return false | ||
} | ||
return true | ||
}), | ||
) | ||
} | ||
@@ -175,3 +205,5 @@ | ||
if (err?.isAuthError) throw err | ||
throw new ErrorWithCause(`Could not ${method} ${this.#getUrl(path)}`, { cause: err }) | ||
throw new ErrorWithCause(`Could not ${method} ${this.#getUrl(path)}`, { | ||
cause: err, | ||
}) | ||
} | ||
@@ -200,2 +232,178 @@ } | ||
} | ||
async uploadRemoteFile (file, reqBody, options = {}) { | ||
try { | ||
if (file.serverToken) { | ||
return await this.connectToServerSocket(file, this.getQueue()) | ||
} | ||
const queueRequestSocketToken = this.getQueue().wrapPromiseFunction( | ||
this.#requestSocketToken, | ||
{ priority: -1 }, | ||
) | ||
const serverToken = await queueRequestSocketToken(file, reqBody).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), | ||
this.getQueue(), | ||
) | ||
} catch (err) { | ||
if (err?.cause?.name === 'AbortError') { | ||
// The file upload was aborted, it’s not an error | ||
return undefined | ||
} | ||
this.uppy.setFileState(file.id, { serverToken: undefined }) | ||
this.uppy.emit('upload-error', file, err) | ||
throw err | ||
} | ||
} | ||
#requestSocketToken = async (file, postBody) => { | ||
if (file.remote.url == null) { | ||
throw new Error('Cannot connect to an undefined URL') | ||
} | ||
const res = await this.post(file.remote.url, { | ||
...file.remote.body, | ||
...postBody, | ||
}) | ||
return res.token | ||
} | ||
/** | ||
* @param {UppyFile} file | ||
*/ | ||
async connectToServerSocket (file, queue) { | ||
return new Promise((resolve, reject) => { | ||
const token = file.serverToken | ||
const host = getSocketHost(file.remote.companionUrl) | ||
const socket = new Socket({ | ||
target: `${host}/api/${token}`, | ||
autoOpen: false, | ||
}) | ||
const eventManager = new EventManager(this.uppy) | ||
let queuedRequest | ||
eventManager.onFileRemove(file.id, () => { | ||
socket.send('cancel', {}) | ||
queuedRequest.abort() | ||
resolve(`upload ${file.id} was removed`) | ||
}) | ||
eventManager.onPause(file.id, (isPaused) => { | ||
if (isPaused) { | ||
// Remove this file from the queue so another file can start in its place. | ||
socket.send('pause', {}) | ||
queuedRequest.abort() | ||
} else { | ||
// Resuming an upload should be queued, else you could pause and then | ||
// resume a queued upload to make it skip the queue. | ||
queuedRequest.abort() | ||
queuedRequest = queue.run(() => { | ||
socket.open() | ||
socket.send('resume', {}) | ||
return () => {} | ||
}) | ||
} | ||
}) | ||
eventManager.onPauseAll(file.id, () => { | ||
socket.send('pause', {}) | ||
queuedRequest.abort() | ||
}) | ||
eventManager.onCancelAll(file.id, ({ reason } = {}) => { | ||
if (reason === 'user') { | ||
socket.send('cancel', {}) | ||
queuedRequest.abort() | ||
} | ||
resolve(`upload ${file.id} was canceled`) | ||
}) | ||
eventManager.onResumeAll(file.id, () => { | ||
queuedRequest.abort() | ||
if (file.error) { | ||
socket.send('pause', {}) | ||
} | ||
queuedRequest = queue.run(() => { | ||
socket.open() | ||
socket.send('resume', {}) | ||
return () => {} | ||
}) | ||
}) | ||
eventManager.onRetry(file.id, () => { | ||
// Only do the retry if the upload is actually in progress; | ||
// else we could try to send these messages when the upload is still queued. | ||
// We may need a better check for this since the socket may also be closed | ||
// for other reasons, like network failures. | ||
if (socket.isOpen) { | ||
socket.send('pause', {}) | ||
socket.send('resume', {}) | ||
} | ||
}) | ||
eventManager.onRetryAll(file.id, () => { | ||
// See the comment in the onRetry() call | ||
if (socket.isOpen) { | ||
socket.send('pause', {}) | ||
socket.send('resume', {}) | ||
} | ||
}) | ||
socket.on('progress', (progressData) => emitSocketProgress(this, progressData, file)) | ||
socket.on('error', (errData) => { | ||
const { message } = errData.error | ||
const error = Object.assign(new Error(message), { | ||
cause: errData.error, | ||
}) | ||
// If the remote retry optimisation should not be used, | ||
// close the socket—this will tell companion to clear state and delete the file. | ||
if (!this.opts.useFastRemoteRetry) { | ||
// Remove the serverToken so that a new one will be created for the retry. | ||
this.uppy.setFileState(file.id, { | ||
serverToken: null, | ||
}) | ||
} else { | ||
socket.close() | ||
} | ||
this.uppy.emit('upload-error', file, error) | ||
queuedRequest.done() | ||
reject(error) | ||
}) | ||
socket.on('success', (data) => { | ||
const uploadResp = { | ||
uploadURL: data.url, | ||
} | ||
this.uppy.emit('upload-success', file, uploadResp) | ||
queuedRequest.done() | ||
socket.close() | ||
resolve() | ||
}) | ||
queuedRequest = queue.run(() => { | ||
if (file.isPaused) { | ||
socket.send('pause', {}) | ||
} else { | ||
socket.open() | ||
} | ||
return () => {} | ||
}) | ||
}) | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
121433
1672
Updated@uppy/utils@^5.5.0