mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-17 16:50:22 +00:00
refactor(core): remove duplicate types (#6359)
This PR removes the duplicate companion/provider bridge types (CompanionClientProvider CompanionClientSearchProvider, and the`view: any` escape hatch) and wires the real Provider/ProviderView types directly, now possible since everything lives in `@uppy/core` Net gain in type safety; no behavior change. for more context on the approach see description of #6351
This commit is contained in:
commit
6c05477b65
12 changed files with 100 additions and 125 deletions
|
|
@ -5,14 +5,9 @@ import {
|
|||
type PluginOpts,
|
||||
type Uppy,
|
||||
} from '@uppy/core'
|
||||
import type { RequestOptions } from '@uppy/core/companion-client'
|
||||
import { RequestClient } from '@uppy/core/companion-client'
|
||||
import type {
|
||||
Body,
|
||||
LocalUppyFile,
|
||||
Meta,
|
||||
RequestOptions,
|
||||
UppyFile,
|
||||
} from '@uppy/core/utils'
|
||||
import type { Body, LocalUppyFile, Meta, UppyFile } from '@uppy/core/utils'
|
||||
import {
|
||||
createAbortError,
|
||||
filterFilesToEmitUploadStarted,
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@ import { nanoid } from 'nanoid/non-secure'
|
|||
import type { h } from 'preact'
|
||||
import packageJson from '../package.json' with { type: 'json' }
|
||||
import type BasePlugin from './BasePlugin.js'
|
||||
import type Provider from './companion-client/Provider.js'
|
||||
import type SearchProvider from './companion-client/SearchProvider.js'
|
||||
import getFileName from './getFileName.js'
|
||||
import locale from './locale.js'
|
||||
import { debugLogger, justErrorsLogger } from './loggers.js'
|
||||
import type ProviderView from './provider-views/ProviderView/ProviderView.js'
|
||||
import type { Restrictions, ValidateableFile } from './Restricter.js'
|
||||
import {
|
||||
defaultOptions as defaultRestrictionOptions,
|
||||
|
|
@ -20,8 +23,6 @@ import DefaultStore, { type Store } from './store/index.js'
|
|||
import supportsUploadProgress from './supportsUploadProgress.js'
|
||||
import type {
|
||||
Body,
|
||||
CompanionClientProvider,
|
||||
CompanionClientSearchProvider,
|
||||
CompanionFile,
|
||||
FileProgressNotStarted,
|
||||
FileProgressStarted,
|
||||
|
|
@ -165,8 +166,6 @@ export interface BaseProviderPlugin {
|
|||
* UnknownProviderPlugin can be any Companion plugin (such as Google Drive)
|
||||
* that uses the Companion-assisted OAuth flow.
|
||||
* As the plugins are passed around throughout Uppy we need a generic type for this.
|
||||
* It may seems like duplication, but this type safe. Changing the type of `storage`
|
||||
* will error in the `Provider` class of @uppy/core/companion-client and vice versa.
|
||||
*
|
||||
* Note that this is the *plugin* class, not a version of the `Provider` class.
|
||||
* `Provider` does operate on Companion plugins with `uppy.getPlugin()`.
|
||||
|
|
@ -178,16 +177,25 @@ export type UnknownProviderPlugin<
|
|||
BaseProviderPlugin & {
|
||||
rootFolderId: string | null
|
||||
files: UppyFile<M, B>[]
|
||||
provider: CompanionClientProvider
|
||||
// Can't be typed unfortunately, we can't depend on `provider-views` in `core`.
|
||||
view: any
|
||||
// Structural subset of the real `Provider` class,structural rather than the nominal class type so custom
|
||||
// providers matching the public surface aren't forced to inherit from `Provider`.
|
||||
provider: Pick<
|
||||
Provider<M, B>,
|
||||
| 'name'
|
||||
| 'provider'
|
||||
| 'login'
|
||||
| 'logout'
|
||||
| 'fetchPreAuthToken'
|
||||
| 'fileUrl'
|
||||
| 'list'
|
||||
| 'search'
|
||||
>
|
||||
view: ProviderView<M, B>
|
||||
}
|
||||
|
||||
/*
|
||||
* UnknownSearchProviderPlugin can be any search Companion plugin (such as Unsplash).
|
||||
* As the plugins are passed around throughout Uppy we need a generic type for this.
|
||||
* It may seems like duplication, but this type safe. Changing the type of `title`
|
||||
* will error in the `SearchProvider` class of @uppy/core/companion-client and vice versa.
|
||||
*
|
||||
* Note that this is the *plugin* class, not a version of the `SearchProvider` class.
|
||||
* `SearchProvider` does operate on Companion plugins with `uppy.getPlugin()`.
|
||||
|
|
@ -203,7 +211,11 @@ export type UnknownSearchProviderPlugin<
|
|||
B extends Body,
|
||||
> = UnknownPlugin<M, B, UnknownSearchProviderPluginState> &
|
||||
BaseProviderPlugin & {
|
||||
provider: CompanionClientSearchProvider
|
||||
// Structural subset of the real `SearchProvider` class (see note above).
|
||||
provider: Pick<
|
||||
SearchProvider<M, B>,
|
||||
'name' | 'provider' | 'fileUrl' | 'search'
|
||||
>
|
||||
}
|
||||
|
||||
// for better readability
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@ import type {
|
|||
UnknownProviderPlugin,
|
||||
Uppy,
|
||||
} from '../index.js'
|
||||
import {
|
||||
type CompanionClientProvider,
|
||||
getSocketHost,
|
||||
type RequestOptions,
|
||||
} from '../utils/index.js'
|
||||
import { getSocketHost } from '../utils/index.js'
|
||||
import type { CompanionPluginOptions } from './index.js'
|
||||
import RequestClient, { authErrorStatusCode } from './RequestClient.js'
|
||||
import RequestClient, {
|
||||
authErrorStatusCode,
|
||||
type RequestOptions,
|
||||
} from './RequestClient.js'
|
||||
|
||||
export interface Opts extends PluginOpts, CompanionPluginOptions {
|
||||
pluginId: string
|
||||
|
|
@ -31,10 +30,10 @@ function getOrigin() {
|
|||
return location.origin
|
||||
}
|
||||
|
||||
export default class Provider<M extends Meta, B extends Body>
|
||||
extends RequestClient<M, B>
|
||||
implements CompanionClientProvider
|
||||
{
|
||||
export default class Provider<
|
||||
M extends Meta,
|
||||
B extends Body,
|
||||
> extends RequestClient<M, B> {
|
||||
#refreshingTokenPromise: Promise<void> | undefined
|
||||
|
||||
provider: string
|
||||
|
|
@ -273,11 +272,13 @@ export default class Provider<M extends Meta, B extends Body>
|
|||
}
|
||||
|
||||
async login({
|
||||
uppyVersions,
|
||||
uppyVersions = '',
|
||||
authFormData,
|
||||
signal,
|
||||
}: {
|
||||
uppyVersions: string
|
||||
// `uppyVersions` is optional because callers such as `ProviderView` do not
|
||||
// have it on hand; the OAuth/simple-auth helpers always receive a string.
|
||||
uppyVersions?: string
|
||||
authFormData: unknown
|
||||
signal: AbortSignal
|
||||
}): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
import pRetry, { AbortError } from 'p-retry'
|
||||
import packageJson from '../../package.json' with { type: 'json' }
|
||||
import type Uppy from '../index.js'
|
||||
import type {
|
||||
Body,
|
||||
Meta,
|
||||
RemoteUppyFile,
|
||||
RequestOptions,
|
||||
UppyFile,
|
||||
} from '../utils/index.js'
|
||||
import type { Body, Meta, RemoteUppyFile, UppyFile } from '../utils/index.js'
|
||||
import {
|
||||
ErrorWithCause,
|
||||
fetchWithNetworkError,
|
||||
|
|
@ -16,6 +10,15 @@ import {
|
|||
} from '../utils/index.js'
|
||||
import AuthError from './AuthError.js'
|
||||
|
||||
export type RequestOptions = {
|
||||
method?: string
|
||||
data?: Record<string, unknown>
|
||||
skipPostResponse?: boolean
|
||||
signal?: AbortSignal
|
||||
authFormData?: unknown
|
||||
qs?: Record<string, string>
|
||||
}
|
||||
|
||||
type CompanionHeaders = Record<string, string> | undefined
|
||||
|
||||
export type Opts = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import type { Body, Meta, Uppy } from '../index.js'
|
||||
import type { CompanionClientSearchProvider } from '../utils/index.js'
|
||||
import RequestClient, { type Opts } from './RequestClient.js'
|
||||
|
||||
const getName = (id: string): string => {
|
||||
|
|
@ -9,10 +8,10 @@ const getName = (id: string): string => {
|
|||
.join(' ')
|
||||
}
|
||||
|
||||
export default class SearchProvider<M extends Meta, B extends Body>
|
||||
extends RequestClient<M, B>
|
||||
implements CompanionClientSearchProvider
|
||||
{
|
||||
export default class SearchProvider<
|
||||
M extends Meta,
|
||||
B extends Body,
|
||||
> extends RequestClient<M, B> {
|
||||
provider: string
|
||||
|
||||
id: string
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
export type { CompanionPluginOptions } from './CompanionPluginOptions.js'
|
||||
export { default as getAllowedHosts } from './getAllowedHosts.js'
|
||||
export { default as Provider } from './Provider.js'
|
||||
export { default as RequestClient } from './RequestClient.js'
|
||||
export {
|
||||
default as RequestClient,
|
||||
type RequestOptions,
|
||||
} from './RequestClient.js'
|
||||
export { default as SearchProvider } from './SearchProvider.js'
|
||||
export * as tokenStorage from './tokenStorage.js'
|
||||
|
|
|
|||
|
|
@ -66,6 +66,17 @@ const getDefaultState = (
|
|||
|
||||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
|
||||
|
||||
/**
|
||||
* Shape of the responses Companion returns from its `list` and `search`
|
||||
* endpoints. The `Provider` methods are generic, so we pass this as the
|
||||
* expected response type at the call sites.
|
||||
*/
|
||||
type ProviderListResponse = {
|
||||
username: string
|
||||
nextPagePath: string | null
|
||||
items: CompanionFile[]
|
||||
}
|
||||
|
||||
export interface Opts<M extends Meta, B extends Body> {
|
||||
provider: UnknownProviderPlugin<M, B>['provider']
|
||||
viewType: 'list' | 'grid'
|
||||
|
|
@ -248,10 +259,13 @@ export default class ProviderView<M extends Meta, B extends Body> {
|
|||
await this.#withAbort(async (signal) => {
|
||||
const scopePath =
|
||||
currentFolder.type === 'root' ? undefined : currentFolderId
|
||||
const { items } = await this.provider.search!(searchString, {
|
||||
signal,
|
||||
path: scopePath,
|
||||
})
|
||||
const { items } = await this.provider.search<ProviderListResponse>(
|
||||
searchString,
|
||||
{
|
||||
signal,
|
||||
path: scopePath,
|
||||
},
|
||||
)
|
||||
|
||||
// For each searched file, build the entire path (from the root all the way to the leaf node)
|
||||
// This is because we need to make sure all ancestor folders are present in the partialTree before we open the folder or check the file.
|
||||
|
|
@ -389,10 +403,10 @@ export default class ProviderView<M extends Meta, B extends Body> {
|
|||
let currentPagePath = folderId
|
||||
let currentItems: CompanionFile[] = []
|
||||
do {
|
||||
const { username, nextPagePath, items } = await this.provider.list(
|
||||
currentPagePath,
|
||||
{ signal },
|
||||
)
|
||||
const { username, nextPagePath, items } =
|
||||
await this.provider.list<ProviderListResponse>(currentPagePath, {
|
||||
signal,
|
||||
})
|
||||
// It's important to set the username during one of our first fetches
|
||||
this.plugin.setPluginState({ username })
|
||||
|
||||
|
|
@ -478,10 +492,11 @@ export default class ProviderView<M extends Meta, B extends Body> {
|
|||
) {
|
||||
this.isHandlingScroll = true
|
||||
await this.#withAbort(async (signal) => {
|
||||
const { nextPagePath, items } = await this.provider.list(
|
||||
currentFolder.nextPagePath,
|
||||
{ signal },
|
||||
)
|
||||
const { nextPagePath, items } =
|
||||
await this.provider.list<ProviderListResponse>(
|
||||
currentFolder.nextPagePath,
|
||||
{ signal },
|
||||
)
|
||||
const newPartialTree = PartialTreeUtils.afterScrollFolder(
|
||||
partialTree,
|
||||
currentFolderId,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import type { UnknownPlugin } from '../../index.js'
|
||||
import type {
|
||||
UnknownPlugin,
|
||||
UnknownProviderPlugin,
|
||||
UnknownSearchProviderPlugin,
|
||||
} from '../../index.js'
|
||||
import type {
|
||||
Body,
|
||||
CompanionClientProvider,
|
||||
CompanionClientSearchProvider,
|
||||
CompanionFile,
|
||||
Meta,
|
||||
UppyFileNonGhost,
|
||||
|
|
@ -13,7 +15,9 @@ import companionFileToUppyFile from './companionFileToUppyFile.js'
|
|||
const addFiles = <M extends Meta, B extends Body>(
|
||||
companionFiles: CompanionFile[],
|
||||
plugin: UnknownPlugin<M, B>,
|
||||
provider: CompanionClientProvider | CompanionClientSearchProvider,
|
||||
provider:
|
||||
| UnknownProviderPlugin<M, B>['provider']
|
||||
| UnknownSearchProviderPlugin<M, B>['provider'],
|
||||
): void => {
|
||||
const uppyFiles = companionFiles.map((f) =>
|
||||
companionFileToUppyFile<M, B>(f, plugin, provider),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import type { UnknownPlugin } from '../../index.js'
|
||||
import type {
|
||||
UnknownPlugin,
|
||||
UnknownProviderPlugin,
|
||||
UnknownSearchProviderPlugin,
|
||||
} from '../../index.js'
|
||||
import type {
|
||||
Body,
|
||||
CompanionClientProvider,
|
||||
CompanionClientSearchProvider,
|
||||
CompanionFile,
|
||||
Meta,
|
||||
RemoteUppyFile,
|
||||
|
|
@ -11,7 +13,9 @@ import type {
|
|||
const companionFileToUppyFile = <M extends Meta, B extends Body>(
|
||||
file: CompanionFile,
|
||||
plugin: UnknownPlugin<M, B>,
|
||||
provider: CompanionClientProvider | CompanionClientSearchProvider,
|
||||
provider:
|
||||
| UnknownProviderPlugin<M, B>['provider']
|
||||
| UnknownSearchProviderPlugin<M, B>['provider'],
|
||||
): RemoteUppyFile<M, B> => {
|
||||
const name = file.name || file.id
|
||||
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
import type { CompanionFile } from './CompanionFile.js'
|
||||
|
||||
export type RequestOptions = {
|
||||
method?: string
|
||||
data?: Record<string, unknown>
|
||||
skipPostResponse?: boolean
|
||||
signal?: AbortSignal
|
||||
authFormData?: unknown
|
||||
qs?: Record<string, string>
|
||||
}
|
||||
|
||||
/**
|
||||
* CompanionClientProvider is subset of the types of the `Provider`
|
||||
* class from @uppy/core/companion-client.
|
||||
*
|
||||
* This is needed as the `Provider` class is passed around in Uppy and we
|
||||
* need to have shared types for it. Although we are duplicating some types,
|
||||
* this is still safe as `Provider implements CompanionClientProvider`
|
||||
* so any changes here will error there and vice versa.
|
||||
*
|
||||
* TODO: remove this once companion-client and provider-views are merged into a single plugin.
|
||||
*/
|
||||
export interface CompanionClientProvider {
|
||||
name: string
|
||||
provider: string
|
||||
login(options?: RequestOptions): Promise<void>
|
||||
logout<ResBody>(options?: RequestOptions): Promise<ResBody>
|
||||
fetchPreAuthToken(): Promise<void>
|
||||
fileUrl: (a: string) => string
|
||||
list(
|
||||
directory: string | null,
|
||||
options: RequestOptions,
|
||||
): Promise<{
|
||||
username: string
|
||||
nextPagePath: string | null
|
||||
items: CompanionFile[]
|
||||
}>
|
||||
// Optional: not every provider implements server-side search.
|
||||
search?: (
|
||||
text: string,
|
||||
options?: RequestOptions & {
|
||||
path?: string | null | undefined
|
||||
cursor?: string | undefined
|
||||
},
|
||||
) => Promise<{
|
||||
username: string
|
||||
nextPagePath: string | null
|
||||
items: CompanionFile[]
|
||||
}>
|
||||
}
|
||||
export interface CompanionClientSearchProvider {
|
||||
name: string
|
||||
provider: string
|
||||
fileUrl: (a: string) => string
|
||||
search<ResBody>(text: string, queries?: string): Promise<ResBody>
|
||||
}
|
||||
|
|
@ -3,11 +3,6 @@ export {
|
|||
AbortSignal,
|
||||
createAbortError,
|
||||
} from './AbortController.js'
|
||||
export type {
|
||||
CompanionClientProvider,
|
||||
CompanionClientSearchProvider,
|
||||
RequestOptions,
|
||||
} from './CompanionClientProvider.js'
|
||||
export type { CompanionFile } from './CompanionFile.js'
|
||||
|
||||
export { default as canvasToBlob } from './canvasToBlob.js'
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ class WebdavSimpleAuthProvider<M extends Meta, B extends Body> extends Provider<
|
|||
> {
|
||||
async login({
|
||||
authFormData,
|
||||
uppyVersions,
|
||||
uppyVersions = '',
|
||||
signal,
|
||||
}: {
|
||||
uppyVersions: string
|
||||
uppyVersions?: string
|
||||
authFormData: unknown
|
||||
signal: AbortSignal
|
||||
}) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue