mirror of
https://github.com/transloadit/uppy.git
synced 2026-08-02 14:52:24 +00:00
meta: fix all ESLint warnings and turn them into errors (#4398)
This commit is contained in:
parent
6eac99776c
commit
0a7eceac21
40 changed files with 1129 additions and 905 deletions
|
|
@ -3,20 +3,6 @@ lib
|
|||
dist
|
||||
coverage
|
||||
test/lib/**
|
||||
website/public/**
|
||||
website/src/examples/**/*.html
|
||||
website/themes/uppy/source/js/smooth-scroll.min.js
|
||||
website/themes/uppy/source/js/uppy.js
|
||||
website/themes/uppy/source/uppy/**
|
||||
test/endtoend/*/build
|
||||
examples/svelte-example/public/build/
|
||||
bundle-legacy.js
|
||||
website/src/_posts/201*.md
|
||||
website/src/_posts/2020-*.md
|
||||
website/src/_posts/2021-01-*.md
|
||||
website/src/_posts/2021-02-*.md
|
||||
website/src/_posts/2021-03-*.md
|
||||
website/src/_posts/2021-04-*.md
|
||||
website/src/_posts/2021-05-*.md
|
||||
website/src/_posts/2021-06-*.md
|
||||
website/src/docs/react-dashboard.md
|
||||
|
|
|
|||
52
.eslintrc.js
52
.eslintrc.js
|
|
@ -53,8 +53,15 @@ module.exports = {
|
|||
ignore: svgPresentationAttributes,
|
||||
}],
|
||||
|
||||
// Special rules for CI:
|
||||
...(process.env.CI && {
|
||||
// Some imports are available only after a full build, which we don't do on CI.
|
||||
'import/no-unresolved': 'off',
|
||||
}),
|
||||
|
||||
// rules we want to enforce
|
||||
'array-callback-return': 'error',
|
||||
'func-names': 'error',
|
||||
'implicit-arrow-linebreak': 'error',
|
||||
'import/no-dynamic-require': 'error',
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
|
|
@ -76,25 +83,8 @@ module.exports = {
|
|||
'prefer-spread': 'error',
|
||||
'unicorn/prefer-node-protocol': 'error',
|
||||
|
||||
// transloadit rules we would like to enforce in the future
|
||||
// but will require separate PRs to gradually get there
|
||||
// and so the meantime: just warn
|
||||
'class-methods-use-this': ['warn'],
|
||||
'consistent-return': ['warn'],
|
||||
'default-case': ['warn'],
|
||||
'global-require': ['warn'],
|
||||
'import/no-unresolved': ['warn'],
|
||||
'import/order': ['warn'],
|
||||
'no-mixed-operators': ['warn'],
|
||||
'no-param-reassign': ['warn'],
|
||||
'no-redeclare': ['warn'],
|
||||
'no-shadow': ['warn'],
|
||||
'no-use-before-define': ['warn', { 'functions': false }],
|
||||
'radix': ['warn'],
|
||||
'react/button-has-type': 'error',
|
||||
'react/destructuring-assignment': ['warn'],
|
||||
'react/forbid-prop-types': 'error',
|
||||
'react/jsx-props-no-spreading': ['warn'],
|
||||
'react/no-access-state-in-setstate': 'error',
|
||||
'react/no-array-index-key': 'error',
|
||||
'react/no-deprecated': 'error',
|
||||
|
|
@ -122,8 +112,8 @@ module.exports = {
|
|||
// jsdoc
|
||||
'jsdoc/check-alignment': 'error',
|
||||
'jsdoc/check-examples': 'off', // cannot yet be supported for ESLint 8, see https://github.com/eslint/eslint/issues/14745
|
||||
'jsdoc/check-param-names': ['warn'],
|
||||
'jsdoc/check-syntax': ['warn'],
|
||||
'jsdoc/check-param-names': 'error',
|
||||
'jsdoc/check-syntax': 'error',
|
||||
'jsdoc/check-tag-names': ['error', { jsxTags: true }],
|
||||
'jsdoc/check-types': 'error',
|
||||
'jsdoc/newline-after-description': 'error',
|
||||
|
|
@ -253,7 +243,6 @@ module.exports = {
|
|||
'packages/@uppy/webcam/src/**/*.js',
|
||||
'packages/@uppy/xhr-upload/src/**/*.js',
|
||||
'packages/@uppy/zoom/src/**/*.js',
|
||||
'website/src/examples/*/*.es6',
|
||||
],
|
||||
parser: 'espree',
|
||||
parserOptions: {
|
||||
|
|
@ -359,20 +348,10 @@ module.exports = {
|
|||
'no-use-before-define': 'warn',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'website/src/examples/*/*.es6',
|
||||
],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'*.test.js',
|
||||
'test/endtoend/*.js',
|
||||
'website/*.js',
|
||||
'bin/**.js',
|
||||
],
|
||||
rules: {
|
||||
|
|
@ -383,8 +362,8 @@ module.exports = {
|
|||
files: [
|
||||
'bin/**.js',
|
||||
'bin/**.mjs',
|
||||
'examples/**/*.config.js',
|
||||
'examples/**/*.cjs',
|
||||
'examples/**/*.js',
|
||||
'packages/@uppy/companion/test/**/*.js',
|
||||
'test/**/*.js',
|
||||
'test/**/*.ts',
|
||||
|
|
@ -392,8 +371,6 @@ module.exports = {
|
|||
'*.test-d.ts',
|
||||
'postcss.config.js',
|
||||
'.eslintrc.js',
|
||||
'website/*.js',
|
||||
'website/**/*.js',
|
||||
'private/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
|
|
@ -415,15 +392,6 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: [
|
||||
'website/themes/uppy/source/js/*.js',
|
||||
],
|
||||
rules: {
|
||||
'prefer-const': ['off'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: ['test/endtoend/*/*.mjs', 'test/endtoend/*/*.ts'],
|
||||
rules: {
|
||||
|
|
|
|||
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -25,18 +25,6 @@ test/endtoend/create-react-app/coverage/
|
|||
uppy-*.tgz
|
||||
generatedLocale.d.ts
|
||||
|
||||
website/db.json
|
||||
website/*.log
|
||||
website/node_modules/
|
||||
website/public/
|
||||
website/.deploy*/
|
||||
website/src/_drafts
|
||||
website/themes/uppy/source/uppy/
|
||||
website/themes/uppy/_config.yml
|
||||
website/src/_template/list_of_locale_packs.md
|
||||
website/src/examples/locale_list.json
|
||||
website/themes/uppy/layout/partials/generated_stargazers.ejs
|
||||
|
||||
**/output/*
|
||||
!output/.keep
|
||||
examples/dev/file.txt
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ describe('Dashboard with @uppy/aws-s3-multipart', () => {
|
|||
it('should upload cat image successfully', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { force:true })
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click().then(() => {
|
||||
cy.wait(['@post', '@get', '@put']).then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@post', '@get', '@put'])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,10 +9,8 @@ describe('Dashboard with @uppy/aws-s3', () => {
|
|||
it('should upload cat image successfully', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { force:true })
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click().then(() => {
|
||||
cy.wait(['@post', '@get']).then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@post', '@get'])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ describe('Dashboard with Transloadit', () => {
|
|||
it('should upload cat image successfully', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { force:true })
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click().then(() => {
|
||||
cy.wait(['@assemblies', '@resumable']).then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@assemblies', '@resumable']).then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -31,17 +30,16 @@ describe('Dashboard with Transloadit', () => {
|
|||
cy.get('@file-input').selectFile(
|
||||
['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg', 'cypress/fixtures/images/car.jpg'],
|
||||
{ force:true },
|
||||
).then(() => {
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click().then(() => {
|
||||
cy.wait(['@createAssemblies']).then(() => {
|
||||
expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).every((a: any) => a.pollInterval)).to.equal(true)
|
||||
)
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
|
||||
uppy.cancelAll()
|
||||
cy.wait(['@createAssemblies']).then(() => {
|
||||
expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).every((a: any) => a.pollInterval)).to.equal(true)
|
||||
|
||||
cy.wait(['@delete']).then(() => {
|
||||
expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some((a: any) => a.pollInterval)).to.equal(false)
|
||||
})
|
||||
})
|
||||
uppy.cancelAll()
|
||||
|
||||
cy.wait(['@delete']).then(() => {
|
||||
expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some((a: any) => a.pollInterval)).to.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -75,20 +73,15 @@ describe('Dashboard with Transloadit', () => {
|
|||
{ method: 'DELETE', pathname: '/assemblies/*', times: 1 },
|
||||
).as('assemblyDeletion')
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click().then(() => {
|
||||
cy.wait('@assemblyPolling').then(() => {
|
||||
cy.get('button[data-cy=cancel]').click().then(() => {
|
||||
cy.wait('@assemblyDeletion').then(() => {
|
||||
// Unfortunately, waiting on a network request somehow often results in a race condition.
|
||||
// We just want to know wether this is called or not, so waiting for 2 sec to be sure.
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(2000).then(() => {
|
||||
expect(spy).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait('@assemblyPolling')
|
||||
cy.get('button[data-cy=cancel]').click()
|
||||
cy.wait('@assemblyDeletion')
|
||||
// Unfortunately, waiting on a network request somehow often results in a race condition.
|
||||
// We just want to know wether this is called or not, so waiting for 2 sec to be sure.
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(2000)
|
||||
expect(spy).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -116,18 +109,16 @@ describe('Dashboard with Transloadit', () => {
|
|||
{ method: 'DELETE', pathname: '/assemblies/*', times: 1 },
|
||||
).as('assemblyDeletion')
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click().then(() => {
|
||||
cy.wait('@assemblyPolling').then(() => {
|
||||
expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).every((a: any) => a.pollInterval)).to.equal(true)
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait('@assemblyPolling')
|
||||
expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).every((a: any) => a.pollInterval)).to.equal(true)
|
||||
|
||||
const { files } = uppy.getState()
|
||||
uppy.removeFiles(Object.keys(files))
|
||||
const { files } = uppy.getState()
|
||||
uppy.removeFiles(Object.keys(files))
|
||||
|
||||
cy.wait('@assemblyDeletion').then(() => {
|
||||
expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some((a: any) => a.pollInterval)).to.equal(false)
|
||||
expect(spy).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
cy.wait('@assemblyDeletion').then(() => {
|
||||
expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some((a: any) => a.pollInterval)).to.equal(false)
|
||||
expect(spy).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,10 +22,9 @@ describe('Dashboard with Tus', () => {
|
|||
it('should upload cat image successfully', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { force:true })
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click().then(() => {
|
||||
cy.wait(['@post', '@patch']).then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@post', '@patch']).then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -37,10 +36,9 @@ describe('Dashboard with Tus', () => {
|
|||
{ statusCode: 429, body: {} },
|
||||
).as('patch')
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click().then(() => {
|
||||
cy.wait('@tus').then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait('@tus').then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
"@types/jasmine": "~4.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsdoc": "^39.0.0",
|
||||
"eslint-plugin-jsdoc": "^40.0.0",
|
||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||
"jasmine-core": "~4.2.0",
|
||||
"karma": "~6.4.0",
|
||||
|
|
|
|||
|
|
@ -137,11 +137,11 @@ app.get('/s3/multipart/:uploadId', (req, res, next) => {
|
|||
const { key } = req.query
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
return
|
||||
}
|
||||
|
||||
const parts = []
|
||||
listPartsPage(0)
|
||||
|
||||
function listPartsPage (startAt) {
|
||||
client.listParts({
|
||||
|
|
@ -165,6 +165,7 @@ app.get('/s3/multipart/:uploadId', (req, res, next) => {
|
|||
}
|
||||
})
|
||||
}
|
||||
listPartsPage(0)
|
||||
})
|
||||
|
||||
function isValidPart (part) {
|
||||
|
|
|
|||
1
examples/react-native-expo/App.js
vendored
1
examples/react-native-expo/App.js
vendored
|
|
@ -112,6 +112,7 @@ export default function App () {
|
|||
<View style={{ alignItems: 'center' }}>
|
||||
<Image
|
||||
style={styles.logo}
|
||||
// eslint-disable-next-line global-require
|
||||
source={require('./assets/uppy-logo.png')}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"version": "0.0.0",
|
||||
"description": "Extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:",
|
||||
"lint-staged": {
|
||||
"*.js": "eslint",
|
||||
"*.js": "eslint --fix",
|
||||
"*.jsx": "eslint --fix",
|
||||
"*.md": [
|
||||
"remark --silently-ignore -i .remarkignore -foq",
|
||||
|
|
@ -68,8 +68,8 @@
|
|||
"eslint-plugin-compat": "^4.0.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-jest": "^26.0.0",
|
||||
"eslint-plugin-jsdoc": "^39.0.0",
|
||||
"eslint-plugin-jest": "^27.0.0",
|
||||
"eslint-plugin-jsdoc": "^40.0.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-markdown": "^3.0.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-plugin-unicorn": "^43.0.0",
|
||||
"eslint-plugin-unicorn": "^46.0.0",
|
||||
"github-contributors-list": "^1.2.4",
|
||||
"glob": "^8.0.0",
|
||||
"jest": "^29.0.0",
|
||||
|
|
|
|||
|
|
@ -532,8 +532,10 @@ class Uploader {
|
|||
// previously made to providers. Deleting the field would prevent it from getting leaked
|
||||
// to the frontend etc.
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete error.originalRequest
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete error.originalResponse
|
||||
reject(error)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable global-require */
|
||||
module.exports = {
|
||||
callback: require('./callback'),
|
||||
deauthorizationCallback: require('./deauth-callback'),
|
||||
|
|
|
|||
|
|
@ -12,19 +12,22 @@ module.exports = function oauthRedirect (req, res) {
|
|||
const params = qs.stringify(req.query)
|
||||
const { authProvider } = req.companion.provider
|
||||
if (!req.companion.options.server.oauthDomain) {
|
||||
return res.redirect(req.companion.buildURL(`/connect/${authProvider}/callback?${params}`, true))
|
||||
res.redirect(req.companion.buildURL(`/connect/${authProvider}/callback?${params}`, true))
|
||||
return
|
||||
}
|
||||
|
||||
const state = oAuthState.getDynamicStateFromRequest(req)
|
||||
if (!state) {
|
||||
return res.status(400).send('Cannot find state in session')
|
||||
res.status(400).send('Cannot find state in session')
|
||||
return
|
||||
}
|
||||
const handler = oAuthState.getFromState(state, 'companionInstance', req.companion.options.secret)
|
||||
const handlerHostName = (new URL(handler)).host
|
||||
|
||||
if (hasMatch(handlerHostName, req.companion.options.server.validHosts)) {
|
||||
const url = `${handler}/connect/${authProvider}/callback?${params}`
|
||||
return res.redirect(url)
|
||||
res.redirect(url)
|
||||
return
|
||||
}
|
||||
|
||||
res.status(400).send('Invalid Host in state')
|
||||
|
|
|
|||
|
|
@ -28,13 +28,15 @@ module.exports = function s3 (config) {
|
|||
const client = req.companion.s3Client
|
||||
|
||||
if (!client || typeof config.bucket !== 'string') {
|
||||
return res.status(400).json({ error: 'This Companion server does not support uploading to S3' })
|
||||
res.status(400).json({ error: 'This Companion server does not support uploading to S3' })
|
||||
return
|
||||
}
|
||||
|
||||
const metadata = req.query.metadata || {}
|
||||
const key = config.getKey(req, req.query.filename, metadata)
|
||||
if (typeof key !== 'string') {
|
||||
return res.status(500).json({ error: 'S3 uploads are misconfigured: filename returned from `getKey` must be a string' })
|
||||
res.status(500).json({ error: 'S3 uploads are misconfigured: filename returned from `getKey` must be a string' })
|
||||
return
|
||||
}
|
||||
|
||||
const fields = {
|
||||
|
|
@ -45,8 +47,8 @@ module.exports = function s3 (config) {
|
|||
|
||||
if (config.acl != null) fields.acl = config.acl
|
||||
|
||||
Object.keys(metadata).forEach((key) => {
|
||||
fields[`x-amz-meta-${key}`] = metadata[key]
|
||||
Object.keys(metadata).forEach((metadataKey) => {
|
||||
fields[`x-amz-meta-${metadataKey}`] = metadata[metadataKey]
|
||||
})
|
||||
|
||||
client.createPresignedPost({
|
||||
|
|
@ -88,10 +90,12 @@ module.exports = function s3 (config) {
|
|||
const key = config.getKey(req, req.body.filename, req.body.metadata || {})
|
||||
const { type, metadata } = req.body
|
||||
if (typeof key !== 'string') {
|
||||
return res.status(500).json({ error: 's3: filename returned from `getKey` must be a string' })
|
||||
res.status(500).json({ error: 's3: filename returned from `getKey` must be a string' })
|
||||
return
|
||||
}
|
||||
if (typeof type !== 'string') {
|
||||
return res.status(400).json({ error: 's3: content type must be a string' })
|
||||
res.status(400).json({ error: 's3: content type must be a string' })
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
|
|
@ -135,11 +139,11 @@ module.exports = function s3 (config) {
|
|||
const { key } = req.query
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
return
|
||||
}
|
||||
|
||||
let parts = []
|
||||
listPartsPage(0)
|
||||
|
||||
function listPartsPage (startAt) {
|
||||
client.listParts({
|
||||
|
|
@ -159,14 +163,11 @@ module.exports = function s3 (config) {
|
|||
// Get the next page.
|
||||
listPartsPage(data.NextPartNumberMarker)
|
||||
} else {
|
||||
done()
|
||||
res.json(parts)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function done () {
|
||||
res.json(parts)
|
||||
}
|
||||
listPartsPage(0)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -187,10 +188,12 @@ module.exports = function s3 (config) {
|
|||
const { key } = req.query
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
return
|
||||
}
|
||||
if (!parseInt(partNumber, 10)) {
|
||||
return res.status(400).json({ error: 's3: the part number must be a number between 1 and 10000.' })
|
||||
res.status(400).json({ error: 's3: the part number must be a number between 1 and 10000.' })
|
||||
return
|
||||
}
|
||||
|
||||
client.getSignedUrl('uploadPart', {
|
||||
|
|
@ -229,19 +232,20 @@ module.exports = function s3 (config) {
|
|||
const { key, partNumbers } = req.query
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof partNumbers !== 'string') {
|
||||
return res.status(400).json({ error: 's3: the part numbers must be passed as a comma separated query parameter. For example: "?partNumbers=4,6,7,21"' })
|
||||
res.status(400).json({ error: 's3: the part numbers must be passed as a comma separated query parameter. For example: "?partNumbers=4,6,7,21"' })
|
||||
return
|
||||
}
|
||||
|
||||
const partNumbersArray = partNumbers.split(',')
|
||||
partNumbersArray.forEach((partNumber) => {
|
||||
if (!parseInt(partNumber, 10)) {
|
||||
return res.status(400).json({ error: 's3: the part numbers must be a number between 1 and 10000.' })
|
||||
}
|
||||
})
|
||||
if (!partNumbersArray.every((partNumber) => parseInt(partNumber, 10))) {
|
||||
res.status(400).json({ error: 's3: the part numbers must be a number between 1 and 10000.' })
|
||||
return
|
||||
}
|
||||
|
||||
Promise.all(
|
||||
partNumbersArray.map((partNumber) => {
|
||||
|
|
@ -282,7 +286,8 @@ module.exports = function s3 (config) {
|
|||
const { key } = req.query
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
return
|
||||
}
|
||||
|
||||
client.abortMultipartUpload({
|
||||
|
|
@ -318,10 +323,15 @@ module.exports = function s3 (config) {
|
|||
const { parts } = req.body
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
|
||||
return
|
||||
}
|
||||
if (!Array.isArray(parts) || !parts.every(isValidPart)) {
|
||||
return res.status(400).json({ error: 's3: `parts` must be an array of {ETag, PartNumber} objects.' })
|
||||
if (
|
||||
!Array.isArray(parts)
|
||||
|| !parts.every(part => typeof part === 'object' && typeof part?.PartNumber === 'number' && typeof part.ETag === 'string')
|
||||
) {
|
||||
res.status(400).json({ error: 's3: `parts` must be an array of {ETag, PartNumber} objects.' })
|
||||
return
|
||||
}
|
||||
|
||||
client.completeMultipartUpload({
|
||||
|
|
@ -352,7 +362,3 @@ module.exports = function s3 (config) {
|
|||
.post('/multipart/:uploadId/complete', express.json({ limit: '1mb' }), completeMultipartUpload)
|
||||
.delete('/multipart/:uploadId', abortMultipartUpload)
|
||||
}
|
||||
|
||||
function isValidPart (part) {
|
||||
return part && typeof part === 'object' && typeof part.PartNumber === 'number' && typeof part.ETag === 'string'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ module.exports = function sendToken (req, res, next) {
|
|||
const allowedClients = req.companion.options.clients
|
||||
// if no preset clients then allow any client
|
||||
if (!allowedClients || hasMatch(origin, allowedClients) || hasMatch((new URL(origin)).host, allowedClients)) {
|
||||
return res.send(htmlContent(uppyAuthToken, origin))
|
||||
res.send(htmlContent(uppyAuthToken, origin))
|
||||
return
|
||||
}
|
||||
}
|
||||
next()
|
||||
|
|
|
|||
|
|
@ -37,6 +37,29 @@ module.exports = (redisUrl, redisPubSubScope) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an event listener
|
||||
*
|
||||
* @param {string} eventName name of the event
|
||||
* @param {any} handler the handler of the event to remove
|
||||
*/
|
||||
function removeListener (eventName, handler) {
|
||||
if (eventName === 'error') return errorEmitter.removeListener('error', handler)
|
||||
|
||||
return runWhenConnected(() => {
|
||||
const handlersByThisEventName = handlersByEvent.get(eventName)
|
||||
if (handlersByThisEventName == null) return undefined
|
||||
|
||||
const actualHandler = handlersByThisEventName.get(handler)
|
||||
if (actualHandler == null) return undefined
|
||||
|
||||
handlersByThisEventName.delete(handler)
|
||||
if (handlersByThisEventName.size === 0) handlersByEvent.delete(eventName)
|
||||
|
||||
return subscriber.pUnsubscribe(getPrefixedEventName(eventName), actualHandler)
|
||||
})
|
||||
}
|
||||
|
||||
function addListener (eventName, handler, _once = false) {
|
||||
function actualHandler (message) {
|
||||
if (_once) removeListener(eventName, handler)
|
||||
|
|
@ -92,29 +115,6 @@ module.exports = (redisUrl, redisPubSubScope) => {
|
|||
runWhenConnected(() => publisher.publish(getPrefixedEventName(eventName), JSON.stringify(args)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an event listener
|
||||
*
|
||||
* @param {string} eventName name of the event
|
||||
* @param {any} handler the handler of the event to remove
|
||||
*/
|
||||
function removeListener (eventName, handler) {
|
||||
if (eventName === 'error') return errorEmitter.removeListener('error', handler)
|
||||
|
||||
return runWhenConnected(() => {
|
||||
const handlersByThisEventName = handlersByEvent.get(eventName)
|
||||
if (handlersByThisEventName == null) return undefined
|
||||
|
||||
const actualHandler = handlersByThisEventName.get(handler)
|
||||
if (actualHandler == null) return undefined
|
||||
|
||||
handlersByThisEventName.delete(handler)
|
||||
if (handlersByThisEventName.size === 0) handlersByEvent.delete(eventName)
|
||||
|
||||
return subscriber.pUnsubscribe(getPrefixedEventName(eventName), actualHandler)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all listeners of an event
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@ const crypto = require('node:crypto')
|
|||
const atob = require('atob')
|
||||
const { encrypt, decrypt } = require('./utils')
|
||||
|
||||
const setState = (state, secret) => {
|
||||
const encodedState = Buffer.from(JSON.stringify(state)).toString('base64')
|
||||
return encrypt(encodedState, secret)
|
||||
}
|
||||
|
||||
const getState = (state, secret) => {
|
||||
const encodedState = decrypt(state, secret)
|
||||
return JSON.parse(atob(encodedState))
|
||||
}
|
||||
|
||||
module.exports.generateState = (secret) => {
|
||||
const state = {}
|
||||
state.id = crypto.randomBytes(10).toString('hex')
|
||||
|
|
@ -17,16 +27,6 @@ module.exports.getFromState = (state, name, secret) => {
|
|||
return getState(state, secret)[name]
|
||||
}
|
||||
|
||||
const setState = (state, secret) => {
|
||||
const encodedState = Buffer.from(JSON.stringify(state)).toString('base64')
|
||||
return encrypt(encodedState, secret)
|
||||
}
|
||||
|
||||
const getState = (state, secret) => {
|
||||
const encodedState = decrypt(state, secret)
|
||||
return JSON.parse(atob(encodedState))
|
||||
}
|
||||
|
||||
module.exports.getDynamicStateFromRequest = (req) => {
|
||||
const dynamic = (req.session.grant || {}).dynamic || {}
|
||||
const { state } = dynamic
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ exports.jsonStringify = (data) => {
|
|||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.indexOf(value) !== -1) {
|
||||
// Circular reference found, discard key
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
cache.push(value)
|
||||
}
|
||||
|
|
@ -90,8 +90,7 @@ function urlEncode (unencoded) {
|
|||
}
|
||||
|
||||
function urlDecode (encoded) {
|
||||
encoded = encoded.replace(/-/g, '+').replace(/_/g, '/').replace(/~/g, '=')
|
||||
return encoded
|
||||
return encoded.replace(/-/g, '+').replace(/_/g, '/').replace(/~/g, '=')
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ const semver = require('semver')
|
|||
* @returns {boolean}
|
||||
*/
|
||||
exports.gte = (v1, v2) => {
|
||||
v1 = semver.coerce(v1).version
|
||||
v2 = semver.coerce(v2).version
|
||||
|
||||
return semver.gte(v1, v2)
|
||||
return semver.gte(
|
||||
semver.coerce(v1).version,
|
||||
semver.coerce(v2).version,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,17 +10,6 @@ const logger = require('./logger')
|
|||
// TODO rewrite to use require('timers/promises').setTimeout when we support newer node versions
|
||||
const sleep = promisify(setTimeout)
|
||||
|
||||
/**
|
||||
* Runs a function every 24 hours, to clean up stale, upload related files.
|
||||
*
|
||||
* @param {string} dirPath path to the directory which you want to clean
|
||||
*/
|
||||
exports.startCleanUpJob = (dirPath) => {
|
||||
logger.info('starting clean up job', 'jobs.cleanup.start')
|
||||
// run once a day
|
||||
schedule.scheduleJob('0 23 * * *', () => cleanUpFinishedUploads(dirPath))
|
||||
}
|
||||
|
||||
const cleanUpFinishedUploads = (dirPath) => {
|
||||
logger.info(`running clean up job for path: ${dirPath}`, 'jobs.cleanup.progress.read')
|
||||
fs.readdir(dirPath, (err, files) => {
|
||||
|
|
@ -40,12 +29,12 @@ const cleanUpFinishedUploads = (dirPath) => {
|
|||
}
|
||||
const fullPath = path.join(dirPath, file)
|
||||
|
||||
fs.stat(fullPath, (err, stats) => {
|
||||
fs.stat(fullPath, (err2, stats) => {
|
||||
const twelveHoursAgo = 12 * 60 * 60 * 1000
|
||||
if (err) {
|
||||
if (err2) {
|
||||
// we still delete the file if we can't get the stats
|
||||
// but we also log the error
|
||||
logger.error(err, 'jobs.cleanup.stat.error')
|
||||
logger.error(err2, 'jobs.cleanup.stat.error')
|
||||
// @ts-ignore
|
||||
} else if (((new Date()) - stats.mtime) < twelveHoursAgo) {
|
||||
logger.info(`skipping file ${file}`, 'jobs.cleanup.skip')
|
||||
|
|
@ -53,14 +42,25 @@ const cleanUpFinishedUploads = (dirPath) => {
|
|||
}
|
||||
|
||||
logger.info(`deleting file ${file}`, 'jobs.cleanup.progress.delete')
|
||||
fs.unlink(fullPath, (err) => {
|
||||
if (err) logger.error(err, 'jobs.cleanup.delete.error')
|
||||
fs.unlink(fullPath, (err3) => {
|
||||
if (err3) logger.error(err3, 'jobs.cleanup.delete.error')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a function every 24 hours, to clean up stale, upload related files.
|
||||
*
|
||||
* @param {string} dirPath path to the directory which you want to clean
|
||||
*/
|
||||
exports.startCleanUpJob = (dirPath) => {
|
||||
logger.info('starting clean up job', 'jobs.cleanup.start')
|
||||
// run once a day
|
||||
schedule.scheduleJob('0 23 * * *', () => cleanUpFinishedUploads(dirPath))
|
||||
}
|
||||
|
||||
async function runPeriodicPing ({ urls, payload, requestTimeout }) {
|
||||
// Run requests in parallel
|
||||
await Promise.all(urls.map(async (url) => {
|
||||
|
|
|
|||
|
|
@ -89,9 +89,11 @@ module.exports.addCustomProviders = (customProviders, providers, grantConfig) =>
|
|||
Object.keys(customProviders).forEach((providerName) => {
|
||||
const customProvider = customProviders[providerName]
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
providers[providerName] = customProvider.module
|
||||
|
||||
if (isOAuthProvider(customProvider.module.authProvider)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig[providerName] = {
|
||||
...customProvider.config,
|
||||
// todo: consider setting these options from a universal point also used
|
||||
|
|
@ -116,6 +118,7 @@ module.exports.addProviderOptions = (companionOptions, grantConfig) => {
|
|||
return
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig.defaults = {
|
||||
host: server.host,
|
||||
protocol: server.protocol,
|
||||
|
|
@ -128,9 +131,12 @@ module.exports.addProviderOptions = (companionOptions, grantConfig) => {
|
|||
const authProvider = providerNameToAuthName(providerName, companionOptions)
|
||||
if (isOAuthProvider(authProvider) && grantConfig[authProvider]) {
|
||||
// explicitly add providerOptions so users don't override other providerOptions.
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig[authProvider].key = providerOptions[providerName].key
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig[authProvider].secret = providerOptions[providerName].secret
|
||||
if (providerOptions[providerName].credentialsURL) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig[authProvider].dynamic = ['key', 'secret', 'redirect_uri']
|
||||
}
|
||||
|
||||
|
|
@ -140,16 +146,20 @@ module.exports.addProviderOptions = (companionOptions, grantConfig) => {
|
|||
// override grant.js redirect uri with companion's custom redirect url
|
||||
const isExternal = !!server.implicitPath
|
||||
const redirectPath = `/${providerName}/redirect`
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig[authProvider].redirect_uri = getURLBuilder(companionOptions)(redirectPath, isExternal)
|
||||
if (oauthDomain) {
|
||||
const fullRedirectPath = getURLBuilder(companionOptions)(redirectPath, isExternal, true)
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig[authProvider].redirect_uri = `${server.protocol}://${oauthDomain}${fullRedirectPath}`
|
||||
}
|
||||
|
||||
if (server.implicitPath) {
|
||||
// no url builder is used for this because grant internally adds the path
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig[authProvider].callback = `${server.implicitPath}${grantConfig[authProvider].callback}`
|
||||
} else if (server.path) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
grantConfig[authProvider].callback = `${server.path}${grantConfig[authProvider].callback}`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class Upload {
|
|||
setTimeout(this._triggerProgressThenSuccess.bind(this), 100)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
abort () {
|
||||
// noop
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/* global jest:false, test:false, expect:false, describe:false */
|
||||
|
||||
const mockOauthState = require('../mockoauthstate')()
|
||||
|
||||
// eslint-disable-next-line import/order
|
||||
const request = require('supertest')
|
||||
const tokenService = require('../../src/server/helpers/jwt')
|
||||
const { getServer } = require('../mockserver')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/* global jest:false, test:false, describe:false, expect:false */
|
||||
|
||||
const { cors } = require('../../src/server/middlewares')
|
||||
|
||||
function testWithMock ({ corsOptions, get = () => {}, origin = 'https://localhost:1234' } = {}) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/* global test:false, expect:false, describe:false, */
|
||||
|
||||
const headerSanitize = require('../../src/server/header-blacklist')
|
||||
|
||||
describe('Header black-list testing', () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* global test:false, expect:false, describe:false, beforeAll:false, */
|
||||
const chalk = require('chalk')
|
||||
const logger = require('../../src/server/logger')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/* global jest:false, test:false, expect:false, describe:false */
|
||||
|
||||
jest.mock('../../src/server/helpers/jwt', () => {
|
||||
return {
|
||||
generateToken: () => {},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/* global test:false, expect:false, describe:false, beforeEach:false */
|
||||
|
||||
const providerManager = require('../../src/server/provider')
|
||||
const { getCompanionOptions } = require('../../src/standalone/helper')
|
||||
const { setDefaultEnv } = require('../mockserver')
|
||||
|
|
@ -10,6 +8,7 @@ let companionOptions
|
|||
describe('Test Provider options', () => {
|
||||
beforeEach(() => {
|
||||
setDefaultEnv()
|
||||
// eslint-disable-next-line global-require
|
||||
grantConfig = require('../../src/config/grant')()
|
||||
companionOptions = getCompanionOptions()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/* global jest:false, test:false, expect:false, describe:false */
|
||||
|
||||
const nock = require('nock')
|
||||
const request = require('supertest')
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ module.exports.getServer = (extraEnv) => {
|
|||
// todo rewrite companion to not use global state
|
||||
// https://github.com/transloadit/uppy/issues/3284
|
||||
jest.resetModules()
|
||||
// eslint-disable-next-line global-require
|
||||
const standalone = require('../src/standalone')
|
||||
const authServer = express()
|
||||
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ es_MX.strings = {
|
|||
zoomOut: 'Alejar',
|
||||
}
|
||||
|
||||
es_MX.pluralize = function (count) {
|
||||
es_MX.pluralize = (count) => {
|
||||
if (count === 1) {
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ hi_IN.strings = {
|
|||
zoomOut: 'ज़ूम आउट',
|
||||
}
|
||||
|
||||
hi_IN.pluralize = function (count) {
|
||||
hi_IN.pluralize = (count) => {
|
||||
if (count === 1) {
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
2
packages/@uppy/react/src/Dashboard.d.ts
vendored
2
packages/@uppy/react/src/Dashboard.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react'
|
||||
import type { DashboardOptions } from '@uppy/dashboard'
|
||||
import { Omit, WithBaseUppyProps } from './CommonTypes'
|
||||
import type { Omit, WithBaseUppyProps } from './CommonTypes'
|
||||
|
||||
// This type is mapped into `DashboardProps` below so IntelliSense doesn't display this big mess of nested types
|
||||
type DashboardPropsInner = Omit<
|
||||
|
|
|
|||
2
packages/@uppy/react/src/DashboardModal.d.ts
vendored
2
packages/@uppy/react/src/DashboardModal.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import type { DashboardOptions } from '@uppy/dashboard'
|
||||
import { Omit, ToUppyProps } from './CommonTypes'
|
||||
import type { Omit, ToUppyProps } from './CommonTypes'
|
||||
|
||||
// This type is mapped into `DashboardModalProps` below so IntelliSense doesn't display this big mess of nested types
|
||||
type DashboardModalPropsInner = {
|
||||
|
|
|
|||
2
packages/@uppy/react/src/DragDrop.d.ts
vendored
2
packages/@uppy/react/src/DragDrop.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import type { DragDropOptions } from '@uppy/drag-drop'
|
||||
import { ToUppyProps } from './CommonTypes'
|
||||
import type { ToUppyProps } from './CommonTypes'
|
||||
|
||||
export type DragDropProps = ToUppyProps<DragDropOptions> & React.BaseHTMLAttributes<HTMLDivElement>
|
||||
|
||||
|
|
|
|||
2
packages/@uppy/react/src/FileInput.d.ts
vendored
2
packages/@uppy/react/src/FileInput.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import type { FileInputOptions } from '@uppy/file-input'
|
||||
import { ToUppyProps } from './CommonTypes'
|
||||
import type { ToUppyProps } from './CommonTypes'
|
||||
|
||||
export type FileInputProps = ToUppyProps<FileInputOptions>
|
||||
|
||||
|
|
|
|||
2
packages/@uppy/react/src/ProgressBar.d.ts
vendored
2
packages/@uppy/react/src/ProgressBar.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import type { ProgressBarOptions } from '@uppy/progress-bar'
|
||||
import { ToUppyProps } from './CommonTypes'
|
||||
import type { ToUppyProps } from './CommonTypes'
|
||||
|
||||
export type ProgressBarProps = ToUppyProps<ProgressBarOptions> & React.BaseHTMLAttributes<HTMLDivElement>
|
||||
|
||||
|
|
|
|||
2
packages/@uppy/react/src/StatusBar.d.ts
vendored
2
packages/@uppy/react/src/StatusBar.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import type { StatusBarOptions } from '@uppy/status-bar'
|
||||
import { ToUppyProps } from './CommonTypes'
|
||||
import type { ToUppyProps } from './CommonTypes'
|
||||
|
||||
export type StatusBarProps = ToUppyProps<StatusBarOptions> & React.BaseHTMLAttributes<HTMLDivElement>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue