From 1bbe4f8bd7b3c107705d4fdb6a10cfff816188d6 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 2 Dec 2024 12:12:22 +0100 Subject: [PATCH] fixup! feat(webdav): initial implementation for a WebDAV simple auth provider --- .../src/server/provider/webdav/common.js | 125 ------------------ .../src/server/provider/webdav/index.js | 111 +++++++++++++++- packages/@uppy/webdav/package.json | 4 +- packages/@uppy/webdav/src/Webdav.tsx | 14 +- 4 files changed, 114 insertions(+), 140 deletions(-) delete mode 100644 packages/@uppy/companion/src/server/provider/webdav/common.js diff --git a/packages/@uppy/companion/src/server/provider/webdav/common.js b/packages/@uppy/companion/src/server/provider/webdav/common.js deleted file mode 100644 index 316f449f91..0000000000 --- a/packages/@uppy/companion/src/server/provider/webdav/common.js +++ /dev/null @@ -1,125 +0,0 @@ -const Provider = require('../Provider') -const logger = require('../../logger') -const { getProtectedHttpAgent, validateURL } = require('../../helpers/request') -const { ProviderApiError, ProviderAuthError } = require('../error') - -/** - * WebdavProvider base class provides implementations that could be shared by simple and oauth providers - */ -class WebdavProvider extends Provider { - async getClientHelper ({ url, ...options }) { - const { allowLocalUrls } = this - if (!validateURL(url, allowLocalUrls)) { - throw new Error('invalid webdav url') - } - const { protocol } = new URL(url) - const HttpAgentClass = getProtectedHttpAgent({ protocol, allowLocalIPs: !allowLocalUrls }) - - const { createClient } = await import('webdav') - return createClient(url, { - ...options, - [`${protocol}Agent`] : new HttpAgentClass(), - }) - } - - async getClient ({ username, token, providerUserSession }) { // eslint-disable-line no-unused-vars,class-methods-use-this - logger.error('call to getUsername is not implemented', 'provider.webdav.getUsername.error') - throw new Error('call to getUsername is not implemented') - // todo: use @returns to specify the return type - return this.getClientHelper() // eslint-disable-line - } - - async getUsername ({ token, providerUserSession }) { // eslint-disable-line no-unused-vars,class-methods-use-this - logger.error('call to getUsername is not implemented', 'provider.webdav.getUsername.error') - throw new Error('call to getUsername is not implemented') - } - - /** @protected */ - // eslint-disable-next-line class-methods-use-this - isAuthenticated () { - throw new Error('Not implemented') - } - - async list ({ directory, token, providerUserSession }) { - return this.withErrorHandling('provider.webdav.list.error', async () => { - // @ts-ignore - if (!this.isAuthenticated({ providerUserSession })) { - throw new ProviderAuthError() - } - - const username = await this.getUsername({ token, providerUserSession }) - const data = { username, items: [] } - const client = await this.getClient({ username, token, providerUserSession }) - - /** @type {any} */ - const dir = await client.getDirectoryContents(directory || '/') - - dir.forEach(item => { - const isFolder = item.type === 'directory' - const requestPath = encodeURIComponent(`${directory || ''}/${item.basename}`) - data.items.push({ - isFolder, - id: requestPath, - name: item.basename, - requestPath, // TODO FIXME - modifiedDate: item.lastmod, // TODO FIXME: convert 'Tue, 04 Jul 2023 13:09:47 GMT' to ISO 8601 - ...(!isFolder && { - mimeType: item.mime, - size: item.size, - thumbnail: null, - - }), - }) - }) - - return data - }) - } - - async download ({ id, token, providerUserSession }) { - return this.withErrorHandling('provider.webdav.download.error', async () => { - // maybe we can avoid this by putting the username in front of the request path/id - const username = await this.getUsername({ token, providerUserSession }) - const client = await this.getClient({ username, token, providerUserSession }) - const stream = client.createReadStream(`/${id}`) - return { stream } - }) - } - - // eslint-disable-next-line - async thumbnail ({ id, providerUserSession }) { - // not implementing this because a public thumbnail from webdav will be used instead - logger.error('call to thumbnail is not implemented', 'provider.webdav.thumbnail.error') - throw new Error('call to thumbnail is not implemented') - } - - // todo fixme implement - // eslint-disable-next-line - async size ({ id, token, providerUserSession }) { - return this.withErrorHandling('provider.webdav.size.error', async () => { - const username = await this.getUsername({ token, providerUserSession }) - const client = await this.getClient({ username, token, providerUserSession }) - - /** @type {any} */ - const stat = await client.stat(id) - return stat.size - }) - } - - // eslint-disable-next-line class-methods-use-this - async withErrorHandling (tag, fn) { - try { - return await fn() - } catch (err) { - let err2 = err - if (err.status === 401) err2 = new ProviderAuthError() - if (err.response) { - err2 = new ProviderApiError('WebDAV API error', err.status) // todo improve (read err?.response?.body readable stream and parse response) - } - logger.error(err2, tag) - throw err2 - } - } -} - -module.exports = WebdavProvider diff --git a/packages/@uppy/companion/src/server/provider/webdav/index.js b/packages/@uppy/companion/src/server/provider/webdav/index.js index 90ee83e53f..ef6d6aab6e 100644 --- a/packages/@uppy/companion/src/server/provider/webdav/index.js +++ b/packages/@uppy/companion/src/server/provider/webdav/index.js @@ -1,6 +1,7 @@ -const { validateURL } = require('../../helpers/request') -const WebdavProvider = require('./common') +const Provider = require('../Provider') +const { getProtectedHttpAgent, validateURL } = require('../../helpers/request') +const { ProviderApiError, ProviderAuthError } = require('../error') const { ProviderUserError } = require('../error') const logger = require('../../logger') @@ -9,7 +10,7 @@ const defaultDirectory = '/' /** * Adapter for WebDAV servers that support simple auth (non-OAuth). */ -class WebdavSimpleAuthProvider extends WebdavProvider { +class WebdavProvider extends Provider { static get hasSimpleAuth () { return true } @@ -30,6 +31,9 @@ class WebdavSimpleAuthProvider extends WebdavProvider { throw new Error('invalid public link url') } + // dynamic import because Comanion currently uses CommonJS and webdav is shipped as ESM + // can be implemented as regular require as soon as Node 20.17 or 22 is required + // or as regular import when Companion is ported to ESM const { AuthType } = await import('webdav') // eslint-disable-line import/no-unresolved // Is this an ownCloud or Nextcloud public link URL? e.g. https://example.com/s/kFy9Lek5sm928xP @@ -75,6 +79,105 @@ class WebdavSimpleAuthProvider extends WebdavProvider { throw err } } + + async getClientHelper ({ url, ...options }) { + const { allowLocalUrls } = this + if (!validateURL(url, allowLocalUrls)) { + throw new Error('invalid webdav url') + } + const { protocol } = new URL(url) + const HttpAgentClass = getProtectedHttpAgent({ protocol, allowLocalIPs: !allowLocalUrls }) + + // dynamic import because Comanion currently uses CommonJS and webdav is shipped as ESM + // can be implemented as regular require as soon as Node 20.17 or 22 is required + // or as regular import when Companion is ported to ESM + const { createClient } = await import('webdav') + return createClient(url, { + ...options, + [`${protocol}Agent`] : new HttpAgentClass(), + }) + } + + async list ({ directory, token, providerUserSession }) { + return this.withErrorHandling('provider.webdav.list.error', async () => { + // @ts-ignore + if (!this.isAuthenticated({ providerUserSession })) { + throw new ProviderAuthError() + } + + const username = await this.getUsername({ token, providerUserSession }) + const data = { username, items: [] } + const client = await this.getClient({ username, token, providerUserSession }) + + /** @type {any} */ + const dir = await client.getDirectoryContents(directory || '/') + + dir.forEach(item => { + const isFolder = item.type === 'directory' + const requestPath = encodeURIComponent(`${directory || ''}/${item.basename}`) + data.items.push({ + isFolder, + id: requestPath, + name: item.basename, + requestPath, // TODO FIXME + modifiedDate: item.lastmod, // TODO FIXME: convert 'Tue, 04 Jul 2023 13:09:47 GMT' to ISO 8601 + ...(!isFolder && { + mimeType: item.mime, + size: item.size, + thumbnail: null, + + }), + }) + }) + + return data + }) + } + + async download ({ id, token, providerUserSession }) { + return this.withErrorHandling('provider.webdav.download.error', async () => { + // maybe we can avoid this by putting the username in front of the request path/id + const username = await this.getUsername({ token, providerUserSession }) + const client = await this.getClient({ username, token, providerUserSession }) + const stream = client.createReadStream(`/${id}`) + return { stream } + }) + } + + // eslint-disable-next-line + async thumbnail ({ id, providerUserSession }) { + // not implementing this because a public thumbnail from webdav will be used instead + logger.error('call to thumbnail is not implemented', 'provider.webdav.thumbnail.error') + throw new Error('call to thumbnail is not implemented') + } + + // todo fixme implement + // eslint-disable-next-line + async size ({ id, token, providerUserSession }) { + return this.withErrorHandling('provider.webdav.size.error', async () => { + const username = await this.getUsername({ token, providerUserSession }) + const client = await this.getClient({ username, token, providerUserSession }) + + /** @type {any} */ + const stat = await client.stat(id) + return stat.size + }) + } + + // eslint-disable-next-line class-methods-use-this + async withErrorHandling (tag, fn) { + try { + return await fn() + } catch (err) { + let err2 = err + if (err.status === 401) err2 = new ProviderAuthError() + if (err.response) { + err2 = new ProviderApiError('WebDAV API error', err.status) // todo improve (read err?.response?.body readable stream and parse response) + } + logger.error(err2, tag) + throw err2 + } + } } -module.exports = WebdavSimpleAuthProvider +module.exports = WebdavProvider diff --git a/packages/@uppy/webdav/package.json b/packages/@uppy/webdav/package.json index d753114830..e3d6e8ee0e 100644 --- a/packages/@uppy/webdav/package.json +++ b/packages/@uppy/webdav/package.json @@ -1,7 +1,7 @@ { "name": "@uppy/webdav", "description": "Import files from WebDAV into Uppy.", - "version": "3.1.1", + "version": "0.1.0", "license": "MIT", "main": "lib/index.js", "types": "types/index.d.ts", @@ -10,7 +10,7 @@ "file uploader", "uppy", "uppy-plugin", - "instagram", + "webdav", "provider", "photos", "videos" diff --git a/packages/@uppy/webdav/src/Webdav.tsx b/packages/@uppy/webdav/src/Webdav.tsx index a23e9fde6e..4bc90df0b4 100644 --- a/packages/@uppy/webdav/src/Webdav.tsx +++ b/packages/@uppy/webdav/src/Webdav.tsx @@ -1,5 +1,5 @@ import { h } from 'preact' -import { useCallback, useState } from 'preact/hooks' +import { useState } from 'preact/hooks' import { UIPlugin } from '@uppy/core' import { Provider, tokenStorage } from '@uppy/companion-client' @@ -22,13 +22,10 @@ class WebdavSimpleAuthProvider extends Provider { const AuthForm = ({ loading, i18n, onAuth }) => { const [webdavUrl, setWebdavUrl] = useState('') - const onSubmit = useCallback( - (e) => { - e.preventDefault() - onAuth({ webdavUrl: webdavUrl.trim() }) - }, - [onAuth, webdavUrl], - ) + const onSubmit = (event) => { + event.preventDefault() + onAuth({ webdavUrl: webdavUrl.trim() }) + } return (
@@ -65,7 +62,6 @@ export default class Webdav extends UIPlugin { this.defaultLocale = locale - console.log(locale) this.i18nInit() this.title = this.i18n('pluginNameWebdav')