Make Generics Optional in uppy.getPlugin (#6057)

fixes #6024.

### Problem
- `getPlugin()` defaults to `UnknownPlugin`, so methods like `openModal`
are not visible , since core is not aware of that plugin type

### Proposed change
- Introduce a types-only registry in core:
- `export interface PluginTypeRegistry<M extends Meta, B extends Body>
{}`
- Overload `getPlugin` to return a precise type when the id is a known
key of the registry.
- add `Dashboard` to  PluginTypeRegistry through module augmentation:
  - `'Dashboard': Dashboard<M, B>`.
- When a project imports from `@uppy/dashboard`, its module augmentation
extends PluginTypeRegistry, adding the correct type into it
- I've added Tests , kept them in a separate file so it's easier to
review , once this approach gets approved I'll add them to
`Uppy.test.ts`

Once this PR gets a positive review I'll add this for other plugins ,
currently only added for `@uppy/dashboard`

**Build with Local tarball can be checked here** 


https://stackblitz.com/~/github.com/qxprakash/uppy-type-test?file=type_test.ts
This commit is contained in:
Prakash 2025-11-17 18:18:54 +05:30 committed by GitHub
parent c788818410
commit 79e6460a6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 320 additions and 45 deletions

View file

@ -0,0 +1,36 @@
---
"@uppy/google-photos-picker": minor
"@uppy/google-drive-picker": minor
"@uppy/thumbnail-generator": minor
"@uppy/golden-retriever": minor
"@uppy/provider-views": minor
"@uppy/remote-sources": minor
"@uppy/screen-capture": minor
"@uppy/google-drive": minor
"@uppy/image-editor": minor
"@uppy/drop-target": minor
"@uppy/transloadit": minor
"@uppy/compressor": minor
"@uppy/status-bar": minor
"@uppy/xhr-upload": minor
"@uppy/dashboard": minor
"@uppy/drag-drop": minor
"@uppy/instagram": minor
"@uppy/facebook": minor
"@uppy/onedrive": minor
"@uppy/unsplash": minor
"@uppy/dropbox": minor
"@uppy/aws-s3": minor
"@uppy/webcam": minor
"@uppy/webdav": minor
"@uppy/audio": minor
"@uppy/core": minor
"@uppy/form": minor
"@uppy/zoom": minor
"@uppy/box": minor
"@uppy/tus": minor
"@uppy/url": minor
---
- Add PluginTypeRegistry and typed getPlugin overload in @uppy/core
- Register plugin ids across packages so uppy.getPlugin('Dashboard' | 'Webcam') returns the concrete plugin type and removes the need to pass generics in getPlugin()

View file

@ -15,6 +15,12 @@ import PermissionsScreen from './PermissionsScreen.js'
import RecordingScreen from './RecordingScreen.js'
import supportsMediaRecorder from './supportsMediaRecorder.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Audio: Audio<M, B>
}
}
export interface AudioOptions extends UIPluginOptions {
showAudioSourceDropdown?: boolean
locale?: LocaleStrings<typeof locale>

View file

@ -9,7 +9,7 @@ import {
} from 'vitest'
import 'whatwg-fetch'
import Core, { type UppyFile } from '@uppy/core'
import Core, { type Meta, type UppyFile } from '@uppy/core'
import nock from 'nock'
import AwsS3Multipart, {
type AwsBody,
@ -36,8 +36,8 @@ describe('AwsS3Multipart', () => {
let opts: Partial<AwsS3MultipartOptions<any, any>>
beforeEach(() => {
const core = new Core<any, AwsBody>().use(AwsS3Multipart)
const awsS3Multipart = core.getPlugin('AwsS3Multipart') as any
const core = new Core<Meta, AwsBody>().use(AwsS3Multipart)
const awsS3Multipart = core.getPlugin('AwsS3Multipart')!
opts = awsS3Multipart.opts
})
@ -125,8 +125,8 @@ describe('AwsS3Multipart', () => {
const awsS3Multipart = core.getPlugin('AwsS3Multipart')!
const err = 'Expected a `endpoint` option'
const file = {}
const opts = {}
const file = {} as unknown as UppyFile<Meta, Record<string, never>>
const opts = {} as any
expect(() => awsS3Multipart.opts.createMultipartUpload(file)).toThrow(err)
expect(() => awsS3Multipart.opts.listParts(file, opts)).toThrow(err)
@ -202,11 +202,11 @@ describe('AwsS3Multipart', () => {
})
describe('without companionUrl (custom main functions)', () => {
let core: Core<any, AwsBody>
let awsS3Multipart: AwsS3Multipart<any, AwsBody>
let core: Core<Meta, AwsBody>
let awsS3Multipart: AwsS3Multipart<Meta, AwsBody>
beforeEach(() => {
core = new Core<any, AwsBody>()
core = new Core<Meta, AwsBody>()
core.use(AwsS3Multipart, {
shouldUseMultipart: true,
limit: 0,
@ -226,7 +226,7 @@ describe('AwsS3Multipart', () => {
}),
listParts: undefined as any,
})
awsS3Multipart = core.getPlugin('AwsS3Multipart') as any
awsS3Multipart = core.getPlugin('AwsS3Multipart')!
})
it('Keeps chunks marked as busy through retries until they complete', async () => {
@ -366,7 +366,6 @@ describe('AwsS3Multipart', () => {
),
listParts: undefined as any,
})
const awsS3Multipart = core.getPlugin('AwsS3Multipart')!
const fileSize = 5 * MB + 1 * MB
core.addFile({
@ -380,7 +379,7 @@ describe('AwsS3Multipart', () => {
await core.upload()
expect(awsS3Multipart.opts.uploadPartBytes.mock.calls.length).toEqual(3)
expect(uploadPartBytes.mock.calls.length).toEqual(3)
})
it('calls `upload-error` when uploadPartBytes fails after all retries', async () => {
@ -406,7 +405,6 @@ describe('AwsS3Multipart', () => {
listParts: undefined as any,
})
const fileSize = 5 * MB + 1 * MB
const awsS3Multipart = core.getPlugin('AwsS3Multipart')!
const uploadErrorMock = vi.fn()
const uploadSuccessMock = vi.fn()
core.on('upload-error', uploadErrorMock)
@ -438,7 +436,7 @@ describe('AwsS3Multipart', () => {
// Catch Promise.all reject
}
expect(awsS3Multipart.opts.uploadPartBytes.mock.calls.length).toEqual(5)
expect(uploadPartBytes.mock.calls.length).toEqual(5)
expect(uploadErrorMock.mock.calls.length).toEqual(1)
expect(uploadSuccessMock.mock.calls.length).toEqual(1) // This fails for me becuase upload returned early.
})
@ -526,8 +524,8 @@ describe('AwsS3Multipart', () => {
})
describe('dynamic companionHeader using setOption', () => {
let core: Core<any, AwsBody>
let awsS3Multipart: AwsS3Multipart<any, AwsBody>
let core: Core<Meta, AwsBody>
let awsS3Multipart: AwsS3Multipart<Meta, AwsBody>
const newToken = 'new token'
it('companionHeader is updated before uploading file', async () => {
@ -536,7 +534,7 @@ describe('AwsS3Multipart', () => {
/* Set up preprocessor */
core.addPreProcessor(() => {
awsS3Multipart =
core.getPlugin<AwsS3Multipart<any, AwsBody>>('AwsS3Multipart')!
core.getPlugin<AwsS3Multipart<Meta, AwsBody>>('AwsS3Multipart')!
awsS3Multipart.setOptions({
endpoint: 'http://localhost',
headers: {

View file

@ -45,6 +45,9 @@ declare module '@uppy/core' {
export interface UppyEventMap<M extends Meta, B extends Body> {
's3-multipart:part-uploaded': PartUploadedCallback<M, B>
}
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
AwsS3Multipart: AwsS3Multipart<M, B>
}
}
function assertServerError<T>(res: T): T {

View file

@ -109,3 +109,9 @@ export default class Box<M extends Meta, B extends Body>
return this.view.render(state)
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Box: Box<M, B>
}
}

View file

@ -11,6 +11,9 @@ declare module '@uppy/core' {
export interface UppyEventMap<M extends Meta, B extends Body> {
'compressor:complete': (file: UppyFile<M, B>[]) => void
}
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Compressor: Compressor<M, B>
}
}
export interface CompressorOpts extends PluginOpts, CompressorJS.Options {

View file

@ -142,6 +142,10 @@ export type UnknownProviderPluginState = {
searchResults?: string[] | undefined
}
// biome-ignore lint/suspicious/noEmptyInterface: PluginTypeRegistry is extended via module augmentation
// biome-ignore lint/correctness/noUnusedVariables: Type parameters are used in module augmentation
export interface PluginTypeRegistry<M extends Meta, B extends Body> {}
export interface AsyncStore {
getItem: (key: string) => Promise<string | null>
setItem: (key: string, value: string) => Promise<void>
@ -1941,12 +1945,21 @@ export class Uppy<
/**
* Find one Plugin by name.
*/
getPlugin<K extends keyof PluginTypeRegistry<M, B>>(
id: K,
): PluginTypeRegistry<M, B>[K] | undefined
getPlugin<T extends UnknownPlugin<M, B> = UnknownPlugin<M, B>>(
id: string,
): T | undefined {
): T | undefined
getPlugin(id: string): UnknownPlugin<M, B> | undefined {
for (const plugins of Object.values(this.#plugins)) {
const foundPlugin = plugins.find((plugin) => plugin.id === id)
if (foundPlugin != null) return foundPlugin as T
if (foundPlugin != null) {
return foundPlugin as UnknownPlugin<M, B>
}
}
return undefined
}

View file

@ -22,6 +22,7 @@ export type {
PartialTreeFolderNode,
PartialTreeFolderRoot,
PartialTreeId,
PluginTypeRegistry,
State,
UnknownPlugin,
UnknownProviderPlugin,

View file

@ -1,5 +1,6 @@
import type { Body, InternalMetadata, LocaleStrings, Meta } from '@uppy/utils'
import { expectTypeOf, test } from 'vitest'
import BasePlugin from './BasePlugin.js'
import UIPlugin, { type UIPluginOptions } from './UIPlugin.js'
import Uppy, { type UnknownPlugin } from './Uppy.js'
@ -68,3 +69,72 @@ test('Meta and Body generic move through the Uppy class', async () => {
await core.upload()
})
class TestRegistryPlugin<M extends Meta, B extends Body> extends BasePlugin<
Record<string, never>,
M,
B
> {
constructor(uppy: Uppy<M, B>) {
super(uppy, {})
this.id = 'TestRegistryPlugin'
this.type = 'acquirer'
}
}
// Augment the Type Registry with our test plugin
declare module './Uppy.js' {
interface PluginTypeRegistry<M extends Meta, B extends Body> {
TestRegistryPlugin: TestRegistryPlugin<M, B>
}
}
test('Type Registry: getPlugin with registered plugin name returns correct type', () => {
const uppy = new Uppy()
uppy.use(TestRegistryPlugin)
// When using a registered plugin name, TypeScript should infer the correct type from PluginTypeRegistry
const plugin = uppy.getPlugin('TestRegistryPlugin')
expectTypeOf(plugin).toEqualTypeOf<
TestRegistryPlugin<Meta, Record<string, never>> | undefined
>()
})
test('Type Registry: getPlugin with unregistered name returns UnknownPlugin', () => {
const uppy = new Uppy()
// When using a non-registered string, should return UnknownPlugin
const plugin = uppy.getPlugin('SomeRandomPlugin')
expectTypeOf(plugin).toEqualTypeOf<
UnknownPlugin<Meta, Record<string, never>> | undefined
>()
})
test('Type Registry: getPlugin with dynamic string returns UnknownPlugin', () => {
const uppy = new Uppy()
const pluginName: string = 'DynamicName'
// Dynamic string should use the fallback overload unlike literal string
const plugin = uppy.getPlugin(pluginName)
expectTypeOf(plugin).toEqualTypeOf<
UnknownPlugin<Meta, Record<string, never>> | undefined
>()
})
test('Type Registry: works with custom Meta and Body types', () => {
type CustomMeta = { userId: string; timestamp: number }
type CustomBody = { encrypted: boolean }
// With custom Meta and Body types
const uppy = new Uppy<CustomMeta, CustomBody>()
uppy.use(TestRegistryPlugin)
const plugin = uppy.getPlugin('TestRegistryPlugin')
expectTypeOf(plugin).toEqualTypeOf<
TestRegistryPlugin<CustomMeta, CustomBody> | undefined
>()
})

View file

@ -40,6 +40,10 @@ declare module '@uppy/core' {
'dashboard:file-edit-complete': DashboardFileEditCompleteCallback<M, B>
'dashboard:close-panel': (id: string | undefined) => void
}
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Dashboard: Dashboard<M, B>
}
}
interface PromiseWithResolvers<T> {

View file

@ -62,7 +62,7 @@ afterEach(async () => {
// this is done to prevent the edgecase when all plugins are removed before dashboard is unmounted from UI
// causing PickerPanelContent to crash
const dashboard = uppy.getPlugin('Dashboard') as Dashboard<any, any>
const dashboard = uppy.getPlugin('Dashboard')
dashboard?.hideAllPanels()
const panelSelector = '[data-uppy-panelType="PickerPanel"]'
if (document.querySelector(panelSelector)) {

View file

@ -12,6 +12,12 @@ import type { ComponentChild, h } from 'preact'
import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
DragDrop: DragDrop<M, B>
}
}
export interface DragDropOptions extends UIPluginOptions {
inputName?: string
allowMultipleFiles?: boolean

View file

@ -3,6 +3,12 @@ import { BasePlugin } from '@uppy/core'
import { getDroppedFiles, toArray } from '@uppy/utils'
import packageJson from '../package.json' with { type: 'json' }
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
DropTarget: DropTarget<M, B>
}
}
export interface DropTargetOptions extends PluginOpts {
target?: HTMLElement | string | null
onDrop?: (event: DragEvent) => void

View file

@ -21,6 +21,12 @@ import { type ComponentChild, h } from 'preact'
import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Dropbox: Dropbox<M, B>
}
}
export type DropboxOptions = CompanionPluginOptions & {
locale?: LocaleStrings<typeof locale>
}

View file

@ -21,6 +21,12 @@ import { type ComponentChild, h } from 'preact'
import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Facebook: Facebook<M, B>
}
}
export type FacebookOptions = CompanionPluginOptions & {
locale?: LocaleStrings<typeof locale>
}

View file

@ -14,6 +14,12 @@ import getFormData from 'get-form-data'
import packageJson from '../package.json' with { type: 'json' }
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Form: Form<M, B>
}
}
type Result<M extends Meta, B extends Body> = Parameters<
UppyEventMap<M, B>['complete']
>[0]

View file

@ -207,9 +207,9 @@ describe('Golden retriever', () => {
// Simulate ghosting of the files by deleting it from store(s)
// @ts-expect-error
;(uppy.getPlugin('GoldenRetriever') as GoldenRetriever<any, any>)[
Symbol.for('uppy test: deleteBlobs')
](fileIds)
uppy
.getPlugin('GoldenRetriever')
[Symbol.for('uppy test: deleteBlobs')](fileIds)
// reload page and recreate Uppy instance
uppy = createUppy({ withPageReload: true }).use(GoldenRetriever)

View file

@ -19,6 +19,9 @@ declare module '@uppy/core' {
export interface UppyEventMap<M extends Meta, B extends Body> {
'restore:plugin-data-changed': (data: Record<string, unknown>) => void
}
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
GoldenRetriever: GoldenRetriever<M, B>
}
}
export interface GoldenRetrieverOptions extends PluginOpts {

View file

@ -15,6 +15,12 @@ import type { LocaleStrings } from '@uppy/utils'
import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
GoogleDrivePicker: GoogleDrivePicker<M, B>
}
}
export type GoogleDrivePickerOptions = CompanionPluginOptions & {
clientId: string
apiKey: string

View file

@ -22,6 +22,12 @@ import packageJson from '../package.json' with { type: 'json' }
import DriveProviderViews from './DriveProviderViews.js'
import locale from './locale.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
GoogleDrive: GoogleDrive<M, B>
}
}
export type GoogleDriveOptions = CompanionPluginOptions & {
locale?: LocaleStrings<typeof locale>
}

View file

@ -15,6 +15,12 @@ import type { LocaleStrings } from '@uppy/utils'
import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
GooglePhotosPicker: GooglePhotosPicker<M, B>
}
}
export type GooglePhotosPickerOptions = CompanionPluginOptions & {
clientId: string
locale?: LocaleStrings<typeof locale>

View file

@ -13,6 +13,12 @@ import packageJson from '../package.json' with { type: 'json' }
import Editor from './Editor.js'
import locale from './locale.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
ImageEditor: ImageEditor<M, B>
}
}
declare global {
namespace preact {
interface Component {

View file

@ -21,6 +21,12 @@ import { type ComponentChild, h } from 'preact'
import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Instagram: Instagram<M, B>
}
}
export type InstagramOptions = CompanionPluginOptions & {
locale?: LocaleStrings<typeof locale>
}

View file

@ -122,3 +122,9 @@ export default class OneDrive<M extends Meta, B extends Body>
return this.view.render(state)
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
OneDrive: OneDrive<M, B>
}
}

View file

@ -82,7 +82,7 @@ class DashboardModal<M extends Meta, B extends Body> extends Component<
uppy.use(DashboardPlugin<M, B>, options)
this.plugin = uppy.getPlugin(options.id) as DashboardPlugin<M, B>
this.plugin = uppy.getPlugin(options.id)!
if (open) {
this.plugin.openModal()
}

View file

@ -122,3 +122,9 @@ export default class RemoteSources<
this.#installedPlugins.clear()
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
RemoteSources: RemoteSources<M, B>
}
}

View file

@ -14,6 +14,12 @@ import locale from './locale.js'
import RecorderScreen from './RecorderScreen.js'
import ScreenRecIcon from './ScreenRecIcon.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
ScreenCapture: ScreenCapture<M, B>
}
}
// Check if screen capturing is supported.
// mediaDevices is supprted on mobile Safari, getDisplayMedia is not
function isScreenRecordingSupported() {

View file

@ -15,6 +15,12 @@ import type { StatusBarOptions } from './StatusBarOptions.js'
import statusBarStates from './StatusBarStates.js'
import StatusBarUI, { type StatusBarUIProps } from './StatusBarUI.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
StatusBar: StatusBar<M, B>
}
}
const speedFilterHalfLife = 2000
const ETAFilterHalfLife = 2000

View file

@ -23,7 +23,7 @@ const installPlugin = () => {
} satisfies DashboardOptions<M, B>;
uppy.use(DashboardPlugin<M, B>, options);
plugin = uppy.getPlugin(options.id) as DashboardPlugin<M, B>;
plugin = uppy.getPlugin(options.id)!;
};
const uninstallPlugin = (uppyInstance: Uppy<M, B> = uppy) => {
if (plugin != null) uppyInstance.removePlugin(plugin);

View file

@ -25,7 +25,7 @@ const installPlugin = () => {
} satisfies DashboardOptions<M, B>;
uppy.use(DashboardPlugin<M, B>, options);
plugin = uppy.getPlugin(options.id) as DashboardPlugin<M, B>;
plugin = uppy.getPlugin(options.id)!;
if (open) plugin.openModal();
};
const uninstallPlugin = (uppyInstance: Uppy<M, B> = uppy) => {

View file

@ -20,7 +20,7 @@ const installPlugin = () => {
} satisfies StatusBarOptions;
uppy.use(StatusBarPlugin<M, B>, options);
plugin = uppy.getPlugin(options.id) as StatusBarPlugin<M, B>;
plugin = uppy.getPlugin(options.id)!;
};
const uninstallPlugin = (uppyInstance: Uppy<M, B> = uppy) => {
if (plugin != null) uppyInstance.removePlugin(plugin);

View file

@ -15,6 +15,9 @@ declare module '@uppy/core' {
'thumbnail:request': (file: UppyFile<M, B>) => void
'thumbnail:cancel': (file: UppyFile<M, B>) => void
}
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
ThumbnailGenerator: ThumbnailGenerator<M, B>
}
}
interface Rotation {
@ -500,3 +503,9 @@ export default class ThumbnailGenerator<
}
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
ThumbnailGenerator: ThumbnailGenerator<M, B>
}
}

View file

@ -132,6 +132,10 @@ declare module '@uppy/core' {
progress_combined?: number
}) => void
}
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Transloadit: Transloadit<M, B>
}
}
declare module '@uppy/utils' {

View file

@ -106,6 +106,12 @@ declare module '@uppy/utils' {
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Tus: Tus<M, B>
}
}
/**
* Tus resumable file uploader
*/

View file

@ -112,3 +112,9 @@ export default class Unsplash<M extends Meta, B extends Body>
this.unmount()
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Unsplash: Unsplash<M, B>
}
}

View file

@ -17,6 +17,12 @@ import locale from './locale.js'
import UrlUI from './UrlUI.js'
import forEachDroppedOrPastedUrl from './utils/forEachDroppedOrPastedUrl.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Url: Url<M, B>
}
}
function UrlIcon() {
return (
<svg

View file

@ -36,7 +36,7 @@ export default defineComponent({
target: containerRef.value,
}
uppy.use(DashboardPlugin, options)
pluginRef.value = uppy.getPlugin(options.id) as DashboardPlugin<any, any>
pluginRef.value = uppy.getPlugin(options.id)!
}
useUppy(onMount, pluginRef, props.uppy, propsRef)

View file

@ -32,7 +32,7 @@ export default defineComponent({
target: containerRef.value,
}
uppy.use(DashboardPlugin, options)
pluginRef.value = uppy.getPlugin(options.id) as DashboardPlugin<any, any>
pluginRef.value = uppy.getPlugin(options.id)!
}
useUppy(onMount, pluginRef, props.uppy, propsRef)

View file

@ -26,7 +26,7 @@ export default defineComponent({
target: containerRef.value,
}
uppy.use(StatusBarPlugin, options)
pluginRef.value = uppy.getPlugin(options.id) as StatusBarPlugin<any, any>
pluginRef.value = uppy.getPlugin(options.id)!
}
useUppy(onMount, pluginRef, props.uppy, propsRef)

View file

@ -1,4 +1,4 @@
import Uppy from '@uppy/core'
import { Uppy } from '@uppy/core'
import { describe, expect, it } from 'vitest'
import Webcam from './index.js'
@ -10,10 +10,9 @@ describe('Webcam', () => {
isTypeSupported: () => true,
}
const uppy = new Uppy<any, any>().use(Webcam)
const uppy = new Uppy().use(Webcam)
expect(
(uppy.getPlugin('Webcam') as Webcam<any, any>).getMediaRecorderOptions()
.mimeType,
uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType,
).not.toBeDefined()
})
@ -27,8 +26,7 @@ describe('Webcam', () => {
preferredVideoMimeType: 'video/webm',
})
expect(
(uppy.getPlugin('Webcam') as Webcam<any, any>).getMediaRecorderOptions()
.mimeType,
uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType,
).toEqual('video/webm')
})
@ -42,8 +40,7 @@ describe('Webcam', () => {
preferredVideoMimeType: 'video/mp4',
})
expect(
(uppy.getPlugin('Webcam') as Webcam<any, any>).getMediaRecorderOptions()
.mimeType,
uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType,
).not.toBeDefined()
})
@ -57,8 +54,7 @@ describe('Webcam', () => {
restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] },
}).use(Webcam)
expect(
(uppy.getPlugin('Webcam') as Webcam<any, any>).getMediaRecorderOptions()
.mimeType,
uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType,
).toEqual('video/mp4')
})
@ -72,8 +68,7 @@ describe('Webcam', () => {
restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] },
}).use(Webcam)
expect(
(uppy.getPlugin('Webcam') as Webcam<any, any>).getMediaRecorderOptions()
.mimeType,
uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType,
).toEqual('video/webm')
})
@ -89,8 +84,7 @@ describe('Webcam', () => {
preferredVideoMimeType: 'video/webm',
})
expect(
(uppy.getPlugin('Webcam') as Webcam<any, any>).getMediaRecorderOptions()
.mimeType,
uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType,
).toEqual('video/webm')
})
@ -104,8 +98,7 @@ describe('Webcam', () => {
restrictions: { allowedFileTypes: ['video/mp4', 'video/webm'] },
}).use(Webcam)
expect(
(uppy.getPlugin('Webcam') as Webcam<any, any>).getMediaRecorderOptions()
.mimeType,
uppy.getPlugin('Webcam')?.getMediaRecorderOptions().mimeType,
).toEqual(undefined)
})
})

View file

@ -20,6 +20,12 @@ import locale from './locale.js'
import PermissionsScreen from './PermissionsScreen.js'
import supportsMediaRecorder from './supportsMediaRecorder.js'
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Webcam: Webcam<M, B>
}
}
/**
* Normalize a MIME type or file extension into a MIME type.
*

View file

@ -163,3 +163,9 @@ export default class Webdav<M extends Meta, B extends Body>
return this.view.render(state)
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
WebDav: Webdav<M, B>
}
}

View file

@ -86,6 +86,12 @@ declare module '@uppy/core' {
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
XHRUpload: XHRUpload<M, B>
}
}
function buildResponseError(
xhr?: XMLHttpRequest,
err?: string | Error | NetworkError,

View file

@ -107,3 +107,9 @@ export default class Zoom<M extends Meta, B extends Body>
return this.view.render(state)
}
}
declare module '@uppy/core' {
export interface PluginTypeRegistry<M extends Meta, B extends Body> {
Zoom: Zoom<M, B>
}
}