Merge "main" into "restore/pr-6284 and resolve conflicts

This commit is contained in:
prakash 2026-07-07 18:19:41 +05:30
commit e51c1ad99b
No known key found for this signature in database
409 changed files with 1193 additions and 2873 deletions

View file

@ -3,13 +3,10 @@
"@uppy/google-photos-picker": patch "@uppy/google-photos-picker": patch
"@uppy/google-drive-picker": patch "@uppy/google-drive-picker": patch
"@uppy/thumbnail-generator": patch "@uppy/thumbnail-generator": patch
"@uppy/companion-client": patch
"@uppy/golden-retriever": patch "@uppy/golden-retriever": patch
"@uppy/image-generator": patch "@uppy/image-generator": patch
"@uppy/provider-views": patch
"@uppy/remote-sources": patch "@uppy/remote-sources": patch
"@uppy/screen-capture": patch "@uppy/screen-capture": patch
"@uppy/store-default": patch
"@uppy/google-drive": patch "@uppy/google-drive": patch
"@uppy/image-editor": patch "@uppy/image-editor": patch
"@uppy/transloadit": patch "@uppy/transloadit": patch
@ -31,7 +28,6 @@
"@uppy/webdav": patch "@uppy/webdav": patch
"@uppy/audio": patch "@uppy/audio": patch
"@uppy/react": patch "@uppy/react": patch
"@uppy/utils": patch
"@uppy/core": patch "@uppy/core": patch
"@uppy/form": patch "@uppy/form": patch
"@uppy/zoom": patch "@uppy/zoom": patch
@ -42,4 +38,4 @@
"uppy": patch "uppy": patch
--- ---
Bump shared runtime dependencies (preact, nanoid, lodash, classnames, shallow-equal, pretty-bytes, p-queue, tus-js-client, @transloadit/types @transloadit/prettier-bytes v1, is-mobile, exifr, compressorjs, rxjs, tslib). Also includes type-only fixes in `@uppy/companion`'s `jwt.ts` and `request.ts` to track `@types/jsonwebtoken` v9 and `@types/node`. Bump shared runtime dependencies (preact, nanoid, lodash, classnames, shallow-equal, pretty-bytes, p-queue, tus-js-client, @transloadit/types @transloadit/prettier-bytes v1, is-mobile, exifr, compressorjs, rxjs, tslib). Also includes type-only fixes in `@uppy/companion`'s `jwt.ts` and `request.ts` to track `@types/jsonwebtoken` v9 and `@types/node`.

View file

@ -1,5 +1,5 @@
--- ---
"@uppy/companion-client": patch "@uppy/core": patch
--- ---
uploadRemoteFile() now queues token request and websocket request as a single job in the request queue. uploadRemoteFile() now queues token request and websocket request as a single job in the request queue.

View file

@ -1,8 +1,8 @@
--- ---
"@uppy/companion-client": major "@uppy/core": major
"@uppy/companion": major "@uppy/companion": major
--- ---
Send token using websocket instead of window.opener. Send token using websocket instead of window.opener.
Breaking in `@uppy/companion-client` because it needs newest version of Companion in order to work. Breaking in `@uppy/core` because it needs newest version of Companion in order to work.
Breaking in `@uppy/companion` because `companion.socket()` now requires `companionOptions` to be passed as the second argument. Breaking in `@uppy/companion` because `companion.socket()` now requires `companionOptions` to be passed as the second argument.

View file

@ -1,8 +1,12 @@
/** @jsx h */ /** @jsx h */
import { getAllowedHosts, Provider, tokenStorage } from '@uppy/companion-client'
import { UIPlugin } from '@uppy/core' import { UIPlugin } from '@uppy/core'
import { ProviderViews } from '@uppy/provider-views' import {
getAllowedHosts,
Provider,
tokenStorage,
} from '@uppy/core/companion-client'
import { ProviderViews } from '@uppy/core/provider-views'
const defaultOptions = {} const defaultOptions = {}

View file

@ -4,11 +4,9 @@
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@uppy/companion-client": "workspace:*",
"@uppy/core": "workspace:*", "@uppy/core": "workspace:*",
"@uppy/dashboard": "workspace:*", "@uppy/dashboard": "workspace:*",
"@uppy/google-drive": "workspace:*", "@uppy/google-drive": "workspace:*",
"@uppy/provider-views": "workspace:*",
"@uppy/tus": "workspace:*", "@uppy/tus": "workspace:*",
"preact": "^10.29.2" "preact": "^10.29.2"
}, },

View file

@ -2,11 +2,7 @@
"extends": ["//"], "extends": ["//"],
"tasks": { "tasks": {
"build": { "build": {
"dependsOn": [ "dependsOn": ["@uppy/core#build", "@uppy/dashboard#build"],
"@uppy/core#build",
"@uppy/dashboard#build",
"@uppy/utils#build"
],
"inputs": [ "inputs": [
"projects/uppy/angular/src/**/*.{js,ts,jsx,tsx}", "projects/uppy/angular/src/**/*.{js,ts,jsx,tsx}",
"package.json", "package.json",

View file

@ -40,7 +40,6 @@
"./package.json": "./package.json" "./package.json": "./package.json"
}, },
"dependencies": { "dependencies": {
"@uppy/utils": "workspace:^",
"preact": "^10.29.2" "preact": "^10.29.2"
}, },
"devDependencies": { "devDependencies": {

View file

@ -7,8 +7,8 @@ import type {
} from '@uppy/core' } from '@uppy/core'
import { UIPlugin } from '@uppy/core' import { UIPlugin } from '@uppy/core'
import type { LocaleStrings } from '@uppy/utils' import type { LocaleStrings } from '@uppy/core/utils'
import { getFileTypeExtension } from '@uppy/utils' import { getFileTypeExtension } from '@uppy/core/utils'
import packageJson from '../package.json' with { type: 'json' } import packageJson from '../package.json' with { type: 'json' }
import locale from './locale.js' import locale from './locale.js'
import PermissionsScreen from './PermissionsScreen.js' import PermissionsScreen from './PermissionsScreen.js'

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils' import type { I18n } from '@uppy/core/utils'
interface DiscardButtonProps { interface DiscardButtonProps {
onDiscard: () => void onDiscard: () => void

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils' import type { I18n } from '@uppy/core/utils'
import type { h } from 'preact' import type { h } from 'preact'
interface PermissionsScreenProps { interface PermissionsScreenProps {

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils' import type { I18n } from '@uppy/core/utils'
interface RecordButtonProps { interface RecordButtonProps {
recording: boolean recording: boolean

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils' import type { I18n } from '@uppy/core/utils'
import { useEffect, useRef } from 'preact/hooks' import { useEffect, useRef } from 'preact/hooks'
import AudioSourceSelect, { import AudioSourceSelect, {
type AudioSourceSelectProps, type AudioSourceSelectProps,

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils' import type { I18n } from '@uppy/core/utils'
interface SubmitButtonProps { interface SubmitButtonProps {
onSubmit: () => void onSubmit: () => void

View file

@ -7,9 +7,6 @@
"include": ["./src/**/*.*"], "include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"], "exclude": ["./src/**/*.test.ts"],
"references": [ "references": [
{
"path": "../utils/tsconfig.build.json"
},
{ {
"path": "../core/tsconfig.build.json" "path": "../core/tsconfig.build.json"
} }

View file

@ -6,9 +6,6 @@
}, },
"include": ["./package.json", "./src/**/*.*"], "include": ["./package.json", "./src/**/*.*"],
"references": [ "references": [
{
"path": "../utils/tsconfig.build.json"
},
{ {
"path": "../core/tsconfig.build.json" "path": "../core/tsconfig.build.json"
} }

View file

@ -37,10 +37,6 @@
".": "./lib/index.js", ".": "./lib/index.js",
"./package.json": "./package.json" "./package.json": "./package.json"
}, },
"dependencies": {
"@uppy/companion-client": "workspace:^",
"@uppy/utils": "workspace:^"
},
"devDependencies": { "devDependencies": {
"@aws-sdk/client-s3": "^3.362.0", "@aws-sdk/client-s3": "^3.362.0",
"@aws-sdk/client-sts": "^3.362.0", "@aws-sdk/client-sts": "^3.362.0",

View file

@ -0,0 +1,444 @@
import type { Body, Meta, UppyFile } from '@uppy/core'
import type {
RateLimitedQueue,
WrapPromiseFunctionType,
} from '@uppy/core/utils'
import type AwsS3Multipart from './index.js'
import type {
AwsS3MultipartOptions,
AwsS3UploadParameters,
uploadPartBytes,
} from './index.js'
import { type Chunk, pausingUploadReason } from './MultipartUploader.js'
import type { UploadPartBytesResult, UploadResult } from './utils.js'
import { throwIfAborted } from './utils.js'
function removeMetadataFromURL(urlString: string) {
const urlObject = new URL(urlString)
urlObject.search = ''
urlObject.hash = ''
return urlObject.href
}
export class HTTPCommunicationQueue<M extends Meta, B extends Body> {
#abortMultipartUpload!: WrapPromiseFunctionType<
AwsS3Multipart<M, B>['abortMultipartUpload']
>
#cache = new WeakMap()
#createMultipartUpload!: WrapPromiseFunctionType<
AwsS3Multipart<M, B>['createMultipartUpload']
>
#fetchSignature!: WrapPromiseFunctionType<AwsS3Multipart<M, B>['signPart']>
#getUploadParameters!: WrapPromiseFunctionType<
AwsS3Multipart<M, B>['getUploadParameters']
>
#listParts!: WrapPromiseFunctionType<AwsS3Multipart<M, B>['listParts']>
#previousRetryDelay!: number
#requests
#retryDelays!: { values: () => Iterator<number> }
#sendCompletionRequest!: WrapPromiseFunctionType<
AwsS3Multipart<M, B>['completeMultipartUpload']
>
#setS3MultipartState
#uploadPartBytes!: WrapPromiseFunctionType<uploadPartBytes>
#getFile
constructor(
requests: RateLimitedQueue,
options: AwsS3MultipartOptions<M, B>,
setS3MultipartState: (file: UppyFile<M, B>, result: UploadResult) => void,
getFile: (file: UppyFile<M, B>) => UppyFile<M, B>,
) {
this.#requests = requests
this.#setS3MultipartState = setS3MultipartState
this.#getFile = getFile
this.setOptions(options)
}
setOptions(options: Partial<AwsS3MultipartOptions<M, B>>): void {
const requests = this.#requests
if ('abortMultipartUpload' in options) {
this.#abortMultipartUpload = requests.wrapPromiseFunction(
options.abortMultipartUpload as any,
{ priority: 1 },
)
}
if ('createMultipartUpload' in options) {
this.#createMultipartUpload = requests.wrapPromiseFunction(
options.createMultipartUpload as any,
{ priority: -1 },
)
}
if ('signPart' in options) {
this.#fetchSignature = requests.wrapPromiseFunction(
options.signPart as any,
)
}
if ('listParts' in options) {
this.#listParts = requests.wrapPromiseFunction(options.listParts as any)
}
if ('completeMultipartUpload' in options) {
this.#sendCompletionRequest = requests.wrapPromiseFunction(
options.completeMultipartUpload as any,
{ priority: 1 },
)
}
if ('retryDelays' in options) {
this.#retryDelays = options.retryDelays ?? []
}
if ('uploadPartBytes' in options) {
this.#uploadPartBytes = requests.wrapPromiseFunction(
options.uploadPartBytes as any,
{ priority: Infinity },
)
}
if ('getUploadParameters' in options) {
this.#getUploadParameters = requests.wrapPromiseFunction(
options.getUploadParameters as any,
)
}
}
async #shouldRetry(err: any, retryDelayIterator: Iterator<number>) {
const requests = this.#requests
const status = err?.source?.status
// TODO: this retry logic is taken out of Tus. We should have a centralized place for retrying,
// perhaps the rate limited queue, and dedupe all plugins with that.
if (status == null) {
return false
}
if (status === 403 && err.message === 'Request has expired') {
if (!requests.isPaused) {
// We don't want to exhaust the retryDelayIterator as long as there are
// more than one request in parallel, to give slower connection a chance
// to catch up with the expiry set in Companion.
if (requests.limit === 1 || this.#previousRetryDelay == null) {
const next = retryDelayIterator.next()
if (next == null || next.done) {
return false
}
// If there are more than 1 request done in parallel, the RLQ limit is
// decreased and the failed request is requeued after waiting for a bit.
// If there is only one request in parallel, the limit can't be
// decreased, so we iterate over `retryDelayIterator` as we do for
// other failures.
// `#previousRetryDelay` caches the value so we can re-use it next time.
this.#previousRetryDelay = next.value
}
// No need to stop the other requests, we just want to lower the limit.
requests.rateLimit(0)
await new Promise((resolve) =>
setTimeout(resolve, this.#previousRetryDelay),
)
}
} else if (status === 429) {
// HTTP 429 Too Many Requests => to avoid the whole download to fail, pause all requests.
if (!requests.isPaused) {
const next = retryDelayIterator.next()
if (next == null || next.done) {
return false
}
requests.rateLimit(next.value)
}
} else if (status > 400 && status < 500 && status !== 409) {
// HTTP 4xx, the server won't send anything, it's doesn't make sense to retry
return false
} else if (typeof navigator !== 'undefined' && navigator.onLine === false) {
// The navigator is offline, let's wait for it to come back online.
if (!requests.isPaused) {
requests.pause()
window.addEventListener(
'online',
() => {
requests.resume()
},
{ once: true },
)
}
} else {
// Other error code means the request can be retried later.
const next = retryDelayIterator.next()
if (next == null || next.done) {
return false
}
await new Promise((resolve) => setTimeout(resolve, next.value))
}
return true
}
async getUploadId(
file: UppyFile<M, B>,
signal: AbortSignal,
): Promise<UploadResult> {
let cachedResult: Promise<UploadResult> | UploadResult | undefined
// As the cache is updated asynchronously, there could be a race condition
// where we just miss a new result so we loop here until we get nothing back,
// at which point it's out turn to create a new cache entry.
for (;;) {
if (file.data == null) throw new Error('File data is empty')
cachedResult = this.#cache.get(file.data)
if (cachedResult == null) break
try {
return await cachedResult
} catch {
// In case of failure, we want to ignore the cached error.
// At this point, either there's a new cached value, or we'll exit the loop a create a new one.
}
}
const promise = this.#createMultipartUpload(this.#getFile(file), signal)
const abortPromise = () => {
if (file.data == null) throw new Error('File data is empty')
promise.abort(signal.reason)
this.#cache.delete(file.data)
}
signal.addEventListener('abort', abortPromise, { once: true })
this.#cache.set(file.data, promise)
promise.then(
async (result) => {
signal.removeEventListener('abort', abortPromise)
this.#setS3MultipartState(file, result)
if (file.data == null) throw new Error('File data is empty')
this.#cache.set(file.data, result)
},
() => {
signal.removeEventListener('abort', abortPromise)
if (file.data == null) throw new Error('File data is empty')
this.#cache.delete(file.data)
},
)
return promise
}
async abortFileUpload(file: UppyFile<M, B>): Promise<void> {
if (file.data == null) throw new Error('File data is empty')
const result = this.#cache.get(file.data)
if (result == null) {
// If the createMultipartUpload request never was made, we don't
// need to send the abortMultipartUpload request.
return
}
// Remove the cache entry right away for follow-up requests do not try to
// use the soon-to-be aborted cached values.
this.#cache.delete(file.data)
this.#setS3MultipartState(file, Object.create(null))
let awaitedResult: UploadResult
try {
awaitedResult = await result
} catch {
// If the cached result rejects, there's nothing to abort.
return
}
await this.#abortMultipartUpload(this.#getFile(file), awaitedResult)
}
async #nonMultipartUpload(
file: UppyFile<M, B>,
chunk: Chunk,
signal?: AbortSignal,
) {
const {
method = 'POST',
url,
fields,
headers,
} = await this.#getUploadParameters(this.#getFile(file), {
signal,
}).abortOn(signal)
let body: FormData | Blob
const data = chunk.getData()
if (method.toUpperCase() === 'POST') {
const formData = new FormData()
Object.entries(fields!).forEach(([key, value]) => {
formData.set(key, value)
})
formData.set('file', data)
body = formData
} else {
body = data
}
const { onProgress, onComplete } = chunk
const result = (await this.#uploadPartBytes({
signature: { url, headers, method } as any,
body,
size: data.size,
onProgress,
onComplete,
signal,
}).abortOn(signal)) as unknown as B // todo this doesn't make sense
// Note: `fields.key` is not returned by old Companion versions.
// See https://github.com/transloadit/uppy/pull/5602
const key = fields?.key
this.#setS3MultipartState(file, { key: key! })
return {
...result,
location:
(result.location as string | undefined) ?? removeMetadataFromURL(url),
bucket: fields?.bucket,
key,
}
}
async uploadFile(
file: UppyFile<M, B>,
chunks: Chunk[],
signal: AbortSignal,
): Promise<B & Partial<UploadPartBytesResult>> {
throwIfAborted(signal)
if (chunks.length === 1 && !chunks[0].shouldUseMultipart) {
return this.#nonMultipartUpload(file, chunks[0], signal)
}
const { uploadId, key } = await this.getUploadId(file, signal)
throwIfAborted(signal)
try {
const parts = await Promise.all(
chunks.map((chunk, i) => this.uploadChunk(file, i + 1, chunk, signal)),
)
throwIfAborted(signal)
return await this.#sendCompletionRequest(
this.#getFile(file),
{ key, uploadId, parts, signal },
signal,
).abortOn(signal)
} catch (err) {
if (err?.cause !== pausingUploadReason && err?.name !== 'AbortError') {
// We purposefully don't wait for the promise and ignore its status,
// because we want the error `err` to bubble up ASAP to report it to the
// user. A failure to abort is not that big of a deal anyway.
this.abortFileUpload(file)
}
throw err
}
}
restoreUploadFile(file: UppyFile<M, B>, uploadIdAndKey: UploadResult): void {
if (file.data == null) throw new Error('File data is empty')
this.#cache.set(file.data, uploadIdAndKey)
}
async resumeUploadFile(
file: UppyFile<M, B>,
chunks: Array<Chunk | null>,
signal: AbortSignal,
): Promise<B> {
throwIfAborted(signal)
if (
chunks.length === 1 &&
chunks[0] != null &&
!chunks[0].shouldUseMultipart
) {
return this.#nonMultipartUpload(file, chunks[0], signal)
}
const { uploadId, key } = await this.getUploadId(file, signal)
throwIfAborted(signal)
const alreadyUploadedParts = await this.#listParts(
this.#getFile(file),
{ uploadId, key, signal },
signal,
).abortOn(signal)
throwIfAborted(signal)
const parts = await Promise.all(
chunks.map((chunk, i) => {
const partNumber = i + 1
const alreadyUploadedInfo = alreadyUploadedParts.find(
({ PartNumber }) => PartNumber === partNumber,
)
if (alreadyUploadedInfo == null) {
return this.uploadChunk(file, partNumber, chunk!, signal)
}
// Already uploaded chunks are set to null. If we are restoring the upload, we need to mark it as already uploaded.
chunk?.setAsUploaded?.()
return { PartNumber: partNumber, ETag: alreadyUploadedInfo.ETag }
}),
)
throwIfAborted(signal)
return this.#sendCompletionRequest(
this.#getFile(file),
{ key, uploadId, parts, signal },
signal,
).abortOn(signal)
}
async uploadChunk(
file: UppyFile<M, B>,
partNumber: number,
chunk: Chunk,
signal: AbortSignal,
): Promise<UploadPartBytesResult & { PartNumber: number }> {
throwIfAborted(signal)
const { uploadId, key } = await this.getUploadId(file, signal)
const signatureRetryIterator = this.#retryDelays.values()
const chunkRetryIterator = this.#retryDelays.values()
const shouldRetrySignature = () => {
const next = signatureRetryIterator.next()
if (next == null || next.done) {
return null
}
return next.value
}
for (;;) {
throwIfAborted(signal)
const chunkData = chunk.getData()
const { onProgress, onComplete } = chunk
let signature: AwsS3UploadParameters
try {
signature = await this.#fetchSignature(this.#getFile(file), {
// Always defined for multipart uploads
uploadId: uploadId!,
key,
partNumber,
body: chunkData,
signal,
}).abortOn(signal)
} catch (err) {
const timeout = shouldRetrySignature()
if (timeout == null || signal.aborted) {
throw err
}
await new Promise((resolve) => setTimeout(resolve, timeout))
continue
}
throwIfAborted(signal)
try {
return {
PartNumber: partNumber,
...(await this.#uploadPartBytes({
signature,
body: chunkData,
size: chunkData.size,
onProgress,
onComplete,
signal,
}).abortOn(signal)),
}
} catch (err) {
if (!(await this.#shouldRetry(err, chunkRetryIterator))) throw err
}
}
}
}

View file

@ -0,0 +1,265 @@
import type { Uppy } from '@uppy/core'
import type { Body, Meta, UppyFile } from '@uppy/core/utils'
import { AbortController } from '@uppy/core/utils'
import type { HTTPCommunicationQueue } from './HTTPCommunicationQueue.js'
const MB = 1024 * 1024
interface MultipartUploaderOptions<M extends Meta, B extends Body> {
getChunkSize?: (file: { size: number }) => number
onProgress?: (bytesUploaded: number, bytesTotal: number) => void
onPartComplete?: (part: { PartNumber: number; ETag: string }) => void
shouldUseMultipart?: boolean | ((file: UppyFile<M, B>) => boolean)
onSuccess?: (result: B) => void
onError?: (err: unknown) => void
companionComm: HTTPCommunicationQueue<M, B>
file: UppyFile<M, B>
log: Uppy<M, B>['log']
uploadId?: string
key: string
}
const defaultOptions = {
getChunkSize(file: { size: number }) {
return Math.ceil(file.size / 10000)
},
onProgress() {},
onPartComplete() {},
onSuccess() {},
onError(err: unknown) {
throw err
},
} satisfies Partial<MultipartUploaderOptions<any, any>>
export interface Chunk {
getData: () => Blob
onProgress: (ev: ProgressEvent) => void
onComplete: (etag: string) => void
shouldUseMultipart: boolean
setAsUploaded?: () => void
}
function ensureInt<T>(value: T): T extends number | string ? number : never {
if (typeof value === 'string') {
// @ts-expect-error TS is not able to recognize it's fine.
return parseInt(value, 10)
}
if (typeof value === 'number') {
// @ts-expect-error TS is not able to recognize it's fine.
return value
}
throw new TypeError('Expected a number')
}
export const pausingUploadReason = Symbol('pausing upload, not an actual error')
/**
* A MultipartUploader instance is used per file upload to determine whether a
* upload should be done as multipart or as a regular S3 upload
* (based on the user-provided `shouldUseMultipart` option value) and to manage
* the chunk splitting.
*/
class MultipartUploader<M extends Meta, B extends Body> {
options: MultipartUploaderOptions<M, B> &
Required<Pick<MultipartUploaderOptions<M, B>, keyof typeof defaultOptions>>
#abortController = new AbortController()
#chunks: Array<Chunk | null> = []
#chunkState: { uploaded: number; etag?: string; done?: boolean }[] = []
/**
* The (un-chunked) data to upload.
*/
#data: Blob
#file: UppyFile<M, B>
#uploadHasStarted = false
#onError: (err: unknown) => void
#onSuccess: (result: B) => void
#shouldUseMultipart: MultipartUploaderOptions<M, B>['shouldUseMultipart']
#isRestoring: boolean
#onReject = (err: unknown) =>
(err as any)?.cause === pausingUploadReason ? null : this.#onError(err)
#maxMultipartParts = 10_000
#minPartSize = 5 * MB
constructor(data: Blob, options: MultipartUploaderOptions<M, B>) {
this.options = {
...defaultOptions,
...options,
}
// Use default `getChunkSize` if it was null or something
this.options.getChunkSize ??= defaultOptions.getChunkSize
this.#data = data
this.#file = options.file
this.#onSuccess = this.options.onSuccess
this.#onError = this.options.onError
this.#shouldUseMultipart = this.options.shouldUseMultipart
// When we are restoring an upload, we already have an UploadId and a Key. Otherwise
// we need to call `createMultipartUpload` to get an `uploadId` and a `key`.
// Non-multipart uploads are not restorable.
this.#isRestoring = (options.uploadId && options.key) as any as boolean
this.#initChunks()
}
// initChunks checks the user preference for using multipart uploads (opts.shouldUseMultipart)
// and calculates the optimal part size. When using multipart part uploads every part except for the last has
// to be at least 5 MB and there can be no more than 10K parts.
// This means we sometimes need to change the preferred part size from the user in order to meet these requirements.
#initChunks() {
const fileSize = this.#data.size
const shouldUseMultipart =
typeof this.#shouldUseMultipart === 'function'
? this.#shouldUseMultipart(this.#file)
: Boolean(this.#shouldUseMultipart)
if (shouldUseMultipart && fileSize > this.#minPartSize) {
// At least 5MB per request:
let chunkSize = Math.max(
this.options.getChunkSize(this.#data) as number, // Math.max can take undefined but TS does not think so
this.#minPartSize,
)
let arraySize = Math.floor(fileSize / chunkSize)
// At most 10k requests per file:
if (arraySize > this.#maxMultipartParts) {
arraySize = this.#maxMultipartParts
chunkSize = fileSize / this.#maxMultipartParts
}
this.#chunks = Array(arraySize)
for (let offset = 0, j = 0; offset < fileSize; offset += chunkSize, j++) {
const end = Math.min(fileSize, offset + chunkSize)
// Defer data fetching/slicing until we actually need the data, because it's slow if we have a lot of files
const getData = () => {
const i2 = offset
return this.#data.slice(i2, end)
}
this.#chunks[j] = {
getData,
onProgress: this.#onPartProgress(j),
onComplete: this.#onPartComplete(j),
shouldUseMultipart,
}
if (this.#isRestoring) {
const size =
offset + chunkSize > fileSize ? fileSize - offset : chunkSize
// setAsUploaded is called by listPart, to keep up-to-date the
// quantity of data that is left to actually upload.
this.#chunks[j]!.setAsUploaded = () => {
this.#chunks[j] = null
this.#chunkState[j].uploaded = size
}
}
}
} else {
this.#chunks = [
{
getData: () => this.#data,
onProgress: this.#onPartProgress(0),
onComplete: this.#onPartComplete(0),
shouldUseMultipart,
},
]
}
this.#chunkState = this.#chunks.map(() => ({ uploaded: 0 }))
}
#createUpload() {
this.options.companionComm
.uploadFile(
this.#file,
this.#chunks as Chunk[],
this.#abortController.signal,
)
.then(this.#onSuccess, this.#onReject)
this.#uploadHasStarted = true
}
#resumeUpload() {
this.options.companionComm
.resumeUploadFile(this.#file, this.#chunks, this.#abortController.signal)
.then(this.#onSuccess, this.#onReject)
}
#onPartProgress = (index: number) => (ev: ProgressEvent) => {
if (!ev.lengthComputable) return
this.#chunkState[index].uploaded = ensureInt(ev.loaded)
const totalUploaded = this.#chunkState.reduce((n, c) => n + c.uploaded, 0)
this.options.onProgress(totalUploaded, this.#data.size)
}
#onPartComplete = (index: number) => (etag: string) => {
// This avoids the net::ERR_OUT_OF_MEMORY in Chromium Browsers.
this.#chunks[index] = null
this.#chunkState[index].etag = etag
this.#chunkState[index].done = true
const part = {
PartNumber: index + 1,
ETag: etag,
}
this.options.onPartComplete(part)
}
#abortUpload() {
this.#abortController.abort()
this.options.companionComm
.abortFileUpload(this.#file)
.catch((err: unknown) => this.options.log(err as Error))
}
start(): void {
if (this.#uploadHasStarted) {
if (!this.#abortController.signal.aborted)
this.#abortController.abort(pausingUploadReason)
this.#abortController = new AbortController()
this.#resumeUpload()
} else if (this.#isRestoring) {
this.options.companionComm.restoreUploadFile(this.#file, {
uploadId: this.options.uploadId,
key: this.options.key,
})
this.#resumeUpload()
} else {
this.#createUpload()
}
}
pause(): void {
this.#abortController.abort(pausingUploadReason)
// Swap it out for a new controller, because this instance may be resumed later.
this.#abortController = new AbortController()
}
abort(opts?: { really?: boolean }): void {
if (opts?.really) this.#abortUpload()
else this.pause()
}
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via Symbol in tests
private [Symbol.for('uppy test: getChunkState')]() {
return this.#chunkState
}
}
export default MultipartUploader

View file

@ -7,12 +7,6 @@
"include": ["./src/**/*.*"], "include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"], "exclude": ["./src/**/*.test.ts"],
"references": [ "references": [
{
"path": "../companion-client/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{ {
"path": "../core/tsconfig.build.json" "path": "../core/tsconfig.build.json"
} }

View file

@ -7,12 +7,6 @@
}, },
"include": ["./package.json", "./src/**/*.*"], "include": ["./package.json", "./src/**/*.*"],
"references": [ "references": [
{
"path": "../companion-client/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{ {
"path": "../core/tsconfig.build.json" "path": "../core/tsconfig.build.json"
} }

View file

@ -34,9 +34,6 @@
"./package.json": "./package.json" "./package.json": "./package.json"
}, },
"dependencies": { "dependencies": {
"@uppy/companion-client": "workspace:^",
"@uppy/provider-views": "workspace:^",
"@uppy/utils": "workspace:^",
"preact": "^10.29.2" "preact": "^10.29.2"
}, },
"peerDependencies": { "peerDependencies": {

View file

@ -1,9 +1,3 @@
import {
type CompanionPluginOptions,
getAllowedHosts,
Provider,
tokenStorage,
} from '@uppy/companion-client'
import type { import type {
AsyncStore, AsyncStore,
Body, Body,
@ -13,9 +7,14 @@ import type {
UppyFile, UppyFile,
} from '@uppy/core' } from '@uppy/core'
import { UIPlugin, type Uppy } from '@uppy/core' import { UIPlugin, type Uppy } from '@uppy/core'
import { ProviderViews } from '@uppy/provider-views' import {
type CompanionPluginOptions,
import type { LocaleStrings } from '@uppy/utils' getAllowedHosts,
Provider,
tokenStorage,
} from '@uppy/core/companion-client'
import { ProviderViews } from '@uppy/core/provider-views'
import type { LocaleStrings } from '@uppy/core/utils'
// biome-ignore lint/style/useImportType: h is not a type // biome-ignore lint/style/useImportType: h is not a type
import { type ComponentChild, h } from 'preact' import { type ComponentChild, h } from 'preact'
import packageJson from '../package.json' with { type: 'json' } import packageJson from '../package.json' with { type: 'json' }

View file

@ -7,15 +7,6 @@
"include": ["./src/**/*.*"], "include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"], "exclude": ["./src/**/*.test.ts"],
"references": [ "references": [
{
"path": "../companion-client/tsconfig.build.json"
},
{
"path": "../provider-views/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{ {
"path": "../core/tsconfig.build.json" "path": "../core/tsconfig.build.json"
} }

View file

@ -6,15 +6,6 @@
}, },
"include": ["./package.json", "./src/**/*.*"], "include": ["./package.json", "./src/**/*.*"],
"references": [ "references": [
{
"path": "../companion-client/tsconfig.build.json"
},
{
"path": "../provider-views/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{ {
"path": "../core/tsconfig.build.json" "path": "../core/tsconfig.build.json"
} }

View file

@ -1 +0,0 @@
tsconfig.*

View file

@ -1,301 +0,0 @@
# @uppy/companion-client
## 5.1.1
### Patch Changes
- 0c16fe4: - Split UppyFile into two intefaces distinguished by the `isRemote` boolean:
- LocalUppyFile
- RemoteUppyFile
- Updated dependencies [0c16fe4]
- @uppy/core@5.1.1
- @uppy/utils@7.1.1
## 5.1.0
### Minor Changes
- 5ba2c1c: Introduce the concept of server-side search and add support for it for the Dropbox provider. Previously, only client-side filtering in the currently viewed folder was possible, which was limiting. Now users using Companion with Dropbox can perform a search across their entire account.
### Patch Changes
- Updated dependencies [5ba2c1c]
- @uppy/utils@7.1.0
- @uppy/core@5.1.0
## 5.0.1
### Patch Changes
- 975317d: Removed "main" from package.json, since export maps serve as the contract for the public API.
- Updated dependencies [4b6a76c]
- Updated dependencies [975317d]
- Updated dependencies [9bac4c8]
- @uppy/core@5.0.2
- @uppy/utils@7.0.2
## 5.0.0
### Major Changes
- c5b51f6: ### Export maps for all packages
All packages now have export maps. This is a breaking change in two cases:
1. The css imports have changed from `@uppy[package]/dist/styles.min.css` to `@uppy[package]/css/styles.min.css`
2. You were importing something that wasn't exported from the root, for instance `@uppy/core/lib/foo.js`. You can now only import things we explicitly exported.
#### Changed imports for `@uppy/react`, `@uppy/vue`, and `@uppy/svelte`
Some components, like Dashboard, require a peer dependency to work but since all components were exported from a single file you were forced to install all peer dependencies. Even if you never imported, for instance, the status bar component.
Every component that requires a peer dependency has now been moved to a subpath, such as `@uppy/react/dashboard`, so you only need to install the peer dependencies you need.
**Example for `@uppy/react`:**
**Before:**
```javascript
import { Dashboard, StatusBar } from "@uppy/react";
```
**Now:**
```javascript
import Dashboard from "@uppy/react/dashboard";
import StatusBar from "@uppy/react/status-bar";
```
### Patch Changes
- Updated dependencies [d301c01]
- Updated dependencies [c5b51f6]
- @uppy/utils@7.0.0
- @uppy/core@5.0.0
## 4.5.2
### Patch Changes
- 1b1a9e3: Define "files" in package.json
- Updated dependencies [1b1a9e3]
- @uppy/utils@6.2.2
- @uppy/core@4.5.2
## 4.5.0
### Minor Changes
- 0c24c5a: Use TypeScript compiler instead of Babel
### Patch Changes
- Updated dependencies [0c24c5a]
- Updated dependencies [0c24c5a]
- @uppy/core@4.5.0
- @uppy/utils@6.2.0
## 4.4.2
Released: 2025-05-18
Included in: Uppy v4.16.0
- @uppy/companion-client: don't reject on incorrect origin (Mikael Finstad / #5736)
## 4.4.0
Released: 2025-01-06
Included in: Uppy v4.11.0
- @uppy/angular,@uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive-picker,@uppy/google-drive,@uppy/google-photos-picker,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/webdav,@uppy/xhr-upload,@uppy/zoom: Remove "paths" from all tsconfig's (Merlijn Vos / #5572)
## 4.2.0
Released: 2024-12-05
Included in: Uppy v4.8.0
- @uppy/companion-client: Fix allowed origins (Mikael Finstad / #5536)
- @uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: cleanup tsconfig (Mikael Finstad / #5520)
## 4.1.1
Released: 2024-10-31
Included in: Uppy v4.6.0
- @uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react-native,@uppy/react,@uppy/redux-dev-tools,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/svelte,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: Fix links (Anthony Veaudry / #5492)
## 4.0.0
Released: 2024-07-10
Included in: Uppy v4.0.0
- docs,@uppy/companion-client: don't close socket when pausing (Mikael Finstad / #4821)
## 4.0.0-beta.7
Released: 2024-06-04
Included in: Uppy v4.0.0-beta.10
- @uppy/companion-client: do not allow boolean `RequestOptions` (Mikael Finstad / #5198)
- @uppy/companion-client: remove deprecated options (Mikael Finstad / #5198)
- @uppy/companion-client: make `supportsRefreshToken` default (Mikael Finstad / #5198)
- @uppy/companion-client: remove optional chaining (Mikael Finstad / #5198)
- @uppy/companion-client: remove `Socket` (Mikael Finstad / #5198)
## 4.0.0-beta.6
Released: 2024-05-14
Included in: Uppy v4.0.0-beta.7
- @uppy/companion-client,@uppy/dropbox,@uppy/screen-capture,@uppy/unsplash,@uppy/url,@uppy/webcam: Use `title` consistently from locales (Merlijn Vos / #5134)
## 4.0.0-beta.1
Released: 2024-03-28
Included in: Uppy v4.0.0-beta.1
- @uppy/companion-client,@uppy/provider-views,@uppy/status-bar: fix type imports (Antoine du Hamel / #5038)
- @uppy/companion-client: Replace Provider.initPlugin with composition (Merlijn Vos / #4977)
## 3.8.0
Released: 2024-03-27
Included in: Uppy v3.24.0
- @uppy/box,@uppy/companion-client,@uppy/provider-views,@uppy/status-bar: fix type imports (Antoine du Hamel / #5038)
- @uppy/companion-client: Replace Provider.initPlugin with composition (Merlijn Vos / #4977)
## 3.7.4
Released: 2024-02-28
Included in: Uppy v3.23.0
- @uppy/companion-client,@uppy/utils,@uppy/xhr-upload: improvements for #4922 (Mikael Finstad / #4960)
## 3.7.3
Released: 2024-02-22
Included in: Uppy v3.22.2
- @uppy/companion-client: fix body/url on upload-success (Merlijn Vos / #4922)
- @uppy/companion-client: remove unnecessary `'use strict'` directives (Antoine du Hamel / #4943)
- @uppy/companion-client: type changes for provider-views (Antoine du Hamel / #4938)
- @uppy/companion-client: update types (Antoine du Hamel / #4927)
## 3.7.1
Released: 2024-02-19
Included in: Uppy v3.22.0
- @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/companion-client,@uppy/tus,@uppy/xhr-upload: update `uppyfile` objects before emitting events (antoine du hamel / #4928)
- @uppy/companion-client: fix tests and linter (antoine du hamel / #4890)
- @uppy/companion-client: migrate to ts (merlijn vos / #4864)
- @uppy/companion-client: fix `typeerror` (antoine du hamel)
## 3.7.0
Released: 2023-12-12
Included in: Uppy v3.21.0
- @uppy/companion-client: avoid unnecessary preflight requests (Antoine du Hamel / #4462)
## 3.6.1
Released: 2023-11-24
Included in: Uppy v3.20.0
- @uppy/companion-client: fix log type error (Mikael Finstad / #4766)
- @uppy/companion-client: revert breaking change (Antoine du Hamel / #4801)
## 3.5.0
Released: 2023-10-20
Included in: Uppy v3.18.0
- @uppy/companion-client: fixup! Added Companion OAuth Key type (Murderlon / #4668)
- @uppy/companion-client: Added Companion OAuth Key type (Chris Pratt / #4668)
## 3.4.1
Released: 2023-09-29
Included in: Uppy v3.17.0
- @uppy/companion-client: fix a refresh token race condition (Mikael Finstad / #4695)
## 3.4.0
Released: 2023-09-05
Included in: Uppy v3.15.0
- @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/companion-client,@uppy/core,@uppy/tus,@uppy/utils,@uppy/xhr-upload: Move remote file upload logic into companion-client (Merlijn Vos / #4573)
## 3.3.0
Released: 2023-08-15
Included in: Uppy v3.14.0
- @uppy/companion-client,@uppy/provider-views: make authentication optional (Dominik Schmidt / #4556)
## 3.1.2
Released: 2023-04-04
Included in: Uppy v3.7.0
- @uppy/companion-client: do not open socket more than once (Artur Paikin)
## 3.1.1
Released: 2022-11-16
Included in: Uppy v3.3.1
- @uppy/companion-client: treat `*` the same as missing header (Antoine du Hamel / #4221)
## 3.1.0
Released: 2022-11-10
Included in: Uppy v3.3.0
- @uppy/companion-client: add support for `AbortSignal` (Antoine du Hamel / #4201)
- @uppy/companion-client: prevent preflight race condition (Mikael Finstad / #4182)
## 3.0.2
Released: 2022-09-25
Included in: Uppy v3.1.0
- @uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/companion,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/redux-dev-tools,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/svelte,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: add missing entries to changelog for individual packages (Antoine du Hamel / #4092)
## 3.0.0
Released: 2022-08-22
Included in: Uppy v3.0.0
- Switch to ESM
## 2.2.0
Released: 2022-05-30
Included in: Uppy v2.11.0
- @uppy/companion-client: Revert "Revert "@uppy/companion-client: refactor to ESM"" (Antoine du Hamel / #3730)
## 2.1.0
Released: 2022-05-14
Included in: Uppy v2.10.0
- @uppy/companion-client: refactor to ESM (Antoine du Hamel / #3693)
## 2.0.6
Released: 2022-04-07
Included in: Uppy v2.9.2
- @uppy/aws-s3,@uppy/companion-client,@uppy/transloadit,@uppy/utils: Propagate `isNetworkError` through error wrappers (Renée Kooi / #3620)
## 2.0.5
Released: 2022-02-14
Included in: Uppy v2.5.0
- @uppy/companion-client,@uppy/companion,@uppy/provider-views,@uppy/robodog: Finishing touches on Companion dynamic Oauth (Renée Kooi / #2802)

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018 Transloadit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,55 +0,0 @@
# @uppy/companion-client
<img src="https://uppy.io/img/logo.svg" width="120" alt="Uppy logo: a smiling puppy above a pink upwards arrow" align="right">
[![npm version](https://img.shields.io/npm/v/@uppy/companion-client.svg?style=flat-square)](https://www.npmjs.com/package/@uppy/companion-client)
![CI status for Uppy tests](https://github.com/transloadit/uppy/workflows/CI/badge.svg)
![CI status for Companion tests](https://github.com/transloadit/uppy/workflows/Companion/badge.svg)
![CI status for browser tests](https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg)
Client library for communication with Companion. Intended for use in Uppy
plugins.
Uppy is being developed by the folks at [Transloadit](https://transloadit.com),
a versatile file encoding service.
## Example
```js
import Uppy from '@uppy/core'
import { Provider, RequestClient, Socket } from '@uppy/companion-client'
const uppy = new Uppy()
const client = new RequestClient(uppy, {
companionUrl: 'https://uppy.mywebsite.com/',
})
client.get('/drive/list').then(() => {})
const provider = new Provider(uppy, {
companionUrl: 'https://uppy.mywebsite.com/',
provider: providerPluginInstance,
})
provider.checkAuth().then(() => {})
const socket = new Socket({ target: 'wss://uppy.mywebsite.com/' })
socket.on('progress', () => {})
```
## Installation
> Unless you are writing a custom provider plugin, you do not need to install
> this.
```bash
$ npm install @uppy/companion-client
```
## Documentation
Documentation for this plugin can be found on the
[Uppy website](https://uppy.io/docs/companion).
## License
[The MIT License](./LICENSE).

View file

@ -1,51 +0,0 @@
{
"name": "@uppy/companion-client",
"description": "Client library for communication with Companion. Intended for use in Uppy plugins.",
"version": "5.1.1",
"license": "MIT",
"type": "module",
"sideEffects": false,
"scripts": {
"build": "tsc --build tsconfig.build.json",
"typecheck": "tsc --build",
"test": "vitest run --environment=jsdom --silent='passed-only'"
},
"keywords": [
"file uploader",
"uppy",
"uppy-plugin",
"companion",
"provider"
],
"homepage": "https://uppy.io",
"bugs": {
"url": "https://github.com/transloadit/uppy/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/transloadit/uppy.git"
},
"files": [
"src",
"lib",
"dist",
"CHANGELOG.md"
],
"exports": {
".": "./lib/index.js",
"./package.json": "./package.json"
},
"dependencies": {
"@uppy/utils": "workspace:^",
"namespace-emitter": "^2.0.1",
"p-retry": "^6.1.0"
},
"devDependencies": {
"jsdom": "^29.1.1",
"typescript": "^5.8.3",
"vitest": "^4.1.6"
},
"peerDependencies": {
"@uppy/core": "workspace:^"
}
}

View file

@ -1,17 +0,0 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src"
},
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}

View file

@ -1,16 +0,0 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"emitDeclarationOnly": false,
"noEmit": true
},
"include": ["./package.json", "./src/**/*.*"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}

View file

@ -1,8 +0,0 @@
{
"extends": ["//"],
"tasks": {
"build": {
"dependsOn": ["^build", "@uppy/core#build"]
}
}
}

View file

@ -5,7 +5,7 @@ import type {
UnknownProviderPluginState, UnknownProviderPluginState,
UppyEventMap, UppyEventMap,
} from '@uppy/core' } from '@uppy/core'
import type { ProviderViews } from '@uppy/provider-views' import type { ProviderViews } from '@uppy/core/provider-views'
import { dequal } from 'dequal/lite' import { dequal } from 'dequal/lite'
import { Subscribers } from './utils.js' import { Subscribers } from './utils.js'

View file

@ -18,12 +18,6 @@
}, },
{ {
"path": "../image-editor/tsconfig.build.json" "path": "../image-editor/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
} }
] ]
} }

View file

@ -17,12 +17,6 @@
}, },
{ {
"path": "../image-editor/tsconfig.build.json" "path": "../image-editor/tsconfig.build.json"
},
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
} }
] ]
} }

View file

@ -32,7 +32,6 @@
}, },
"dependencies": { "dependencies": {
"@transloadit/prettier-bytes": "^1.1.0", "@transloadit/prettier-bytes": "^1.1.0",
"@uppy/utils": "workspace:^",
"compressorjs": "^1.3.0", "compressorjs": "^1.3.0",
"preact": "^10.29.2", "preact": "^10.29.2",
"promise-queue": "^2.2.5" "promise-queue": "^2.2.5"

View file

@ -1,7 +1,7 @@
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import Core from '@uppy/core' import Core from '@uppy/core'
import { getFileNameAndExtension } from '@uppy/utils' import { getFileNameAndExtension } from '@uppy/core/utils'
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import CompressorPlugin from './index.js' import CompressorPlugin from './index.js'

View file

@ -1,8 +1,8 @@
import { prettierBytes } from '@transloadit/prettier-bytes' import { prettierBytes } from '@transloadit/prettier-bytes'
import type { DefinePluginOpts, PluginOpts } from '@uppy/core' import type { DefinePluginOpts, PluginOpts } from '@uppy/core'
import { BasePlugin, type Uppy } from '@uppy/core' import { BasePlugin, type Uppy } from '@uppy/core'
import type { Body, LocalUppyFile, Meta, UppyFile } from '@uppy/utils' import type { Body, LocalUppyFile, Meta, UppyFile } from '@uppy/core/utils'
import { getFileNameAndExtension, RateLimitedQueue } from '@uppy/utils' import { getFileNameAndExtension, RateLimitedQueue } from '@uppy/core/utils'
import CompressorJS from 'compressorjs' import CompressorJS from 'compressorjs'
import locale from './locale.js' import locale from './locale.js'

View file

@ -7,9 +7,6 @@
"include": ["./src/**/*.*"], "include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"], "exclude": ["./src/**/*.test.ts"],
"references": [ "references": [
{
"path": "../utils/tsconfig.build.json"
},
{ {
"path": "../core/tsconfig.build.json" "path": "../core/tsconfig.build.json"
} }

View file

@ -7,9 +7,6 @@
}, },
"include": ["./package.json", "./src/**/*.*"], "include": ["./package.json", "./src/**/*.*"],
"references": [ "references": [
{
"path": "../utils/tsconfig.build.json"
},
{ {
"path": "../core/tsconfig.build.json" "path": "../core/tsconfig.build.json"
} }

View file

@ -10,7 +10,7 @@
], ],
"scripts": { "scripts": {
"build": "tsc --build tsconfig.build.json", "build": "tsc --build tsconfig.build.json",
"build:css": "sass --load-path=../../ src/style.scss dist/style.css && postcss dist/style.css -u cssnano -o dist/style.min.css", "build:css": "sass --load-path=../../ src/style.scss dist/style.css && sass --load-path=../../ src/provider-views/style.scss dist/provider-views.css && postcss dist/style.css -u cssnano -o dist/style.min.css && postcss dist/provider-views.css -u cssnano -o dist/provider-views.min.css",
"typecheck": "tsc --build", "typecheck": "tsc --build",
"test": "vitest run --environment=jsdom --silent='passed-only'" "test": "vitest run --environment=jsdom --silent='passed-only'"
}, },
@ -38,20 +38,32 @@
"./css/style.min.css": "./dist/style.min.css", "./css/style.min.css": "./dist/style.min.css",
"./css/style.css": "./dist/style.css", "./css/style.css": "./dist/style.css",
"./css/style.scss": "./src/style.scss", "./css/style.scss": "./src/style.scss",
"./store-default": "./lib/store/index.js",
"./utils": "./lib/utils/index.js",
"./companion-client": "./lib/companion-client/index.js",
"./provider-views": "./lib/provider-views/index.js",
"./provider-views/css/style.min.css": "./dist/provider-views.min.css",
"./provider-views/css/style.css": "./dist/provider-views.css",
"./provider-views/css/style.scss": "./src/provider-views/style.scss",
"./package.json": "./package.json" "./package.json": "./package.json"
}, },
"dependencies": { "dependencies": {
"@transloadit/prettier-bytes": "^1.1.0", "@transloadit/prettier-bytes": "^1.1.0",
"@uppy/store-default": "workspace:^", "classnames": "^2.5.1",
"@uppy/utils": "workspace:^",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"mime-match": "^1.0.2", "mime-match": "^1.0.2",
"namespace-emitter": "^2.0.1", "namespace-emitter": "^2.0.1",
"nanoid": "^5.1.11", "nanoid": "^5.1.11",
"p-queue": "^9.3.0",
"p-retry": "^6.1.0",
"preact": "^10.29.2" "preact": "^10.29.2"
}, },
"devDependencies": { "devDependencies": {
"@types/deep-freeze": "^0", "@types/deep-freeze": "^0",
"@types/gapi": "^0.0.47",
"@types/google.accounts": "^0.0.14",
"@types/google.picker": "^0.0.42",
"@types/lodash": "^4.14.199",
"@types/node": "^20.19.0", "@types/node": "^20.19.0",
"cssnano": "^8.0.1", "cssnano": "^8.0.1",
"deep-freeze": "^0.0.1", "deep-freeze": "^0.0.1",

View file

@ -7,9 +7,14 @@
* See `Plugin` for the extended version with Preact rendering for interfaces. * See `Plugin` for the extended version with Preact rendering for interfaces.
*/ */
import type { Body, I18n, Meta, OptionalPluralizeLocale } from '@uppy/utils'
import { Translator } from '@uppy/utils'
import type { State, UnknownPlugin, Uppy } from './Uppy.js' import type { State, UnknownPlugin, Uppy } from './Uppy.js'
import type {
Body,
I18n,
Meta,
OptionalPluralizeLocale,
} from './utils/index.js'
import { Translator } from './utils/index.js'
export type PluginOpts = { export type PluginOpts = {
locale?: OptionalPluralizeLocale locale?: OptionalPluralizeLocale

View file

@ -1,5 +1,5 @@
import type { Body, Meta, UppyFile } from '@uppy/utils'
import type { _UppyEventMap, Uppy, UppyEventMap } from './Uppy.js' import type { _UppyEventMap, Uppy, UppyEventMap } from './Uppy.js'
import type { Body, Meta, UppyFile } from './utils/index.js'
/** /**
* Create a wrapper around an event emitter with a `remove` method to remove * Create a wrapper around an event emitter with a `remove` method to remove

View file

@ -1,8 +1,8 @@
import { prettierBytes } from '@transloadit/prettier-bytes' import { prettierBytes } from '@transloadit/prettier-bytes'
import type { Body, I18n, Meta, UppyFile } from '@uppy/utils'
// @ts-expect-error untyped // @ts-expect-error untyped
import match from 'mime-match' import match from 'mime-match'
import type { NonNullableUppyOptions, State } from './Uppy.js' import type { NonNullableUppyOptions, State } from './Uppy.js'
import type { Body, I18n, Meta, UppyFile } from './utils/index.js'
export type Restrictions = { export type Restrictions = {
maxFileSize: number | null maxFileSize: number | null

View file

@ -1,9 +1,9 @@
import type { Body, Meta } from '@uppy/utils'
import { findDOMElement, getTextDirection } from '@uppy/utils'
import { render } from 'preact' import { render } from 'preact'
import type { PluginOpts } from './BasePlugin.js' import type { PluginOpts } from './BasePlugin.js'
import BasePlugin from './BasePlugin.js' import BasePlugin from './BasePlugin.js'
import type { State } from './Uppy.js' import type { State } from './Uppy.js'
import type { Body, Meta } from './utils/index.js'
import { findDOMElement, getTextDirection } from './utils/index.js'
/** /**
* Defer a frequent call to the microtask queue. * Defer a frequent call to the microtask queue.

View file

@ -3,7 +3,6 @@ import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { prettierBytes } from '@transloadit/prettier-bytes' import { prettierBytes } from '@transloadit/prettier-bytes'
import type { Body, Meta } from '@uppy/core' import type { Body, Meta } from '@uppy/core'
import type { Locale } from '@uppy/utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import BasePlugin, { import BasePlugin, {
type DefinePluginOpts, type DefinePluginOpts,
@ -21,6 +20,7 @@ import InvalidPluginWithoutType from './mocks/invalidPluginWithoutType.js'
import { RestrictionError } from './Restricter.js' import { RestrictionError } from './Restricter.js'
import UIPlugin from './UIPlugin.js' import UIPlugin from './UIPlugin.js'
import type { State } from './Uppy.js' import type { State } from './Uppy.js'
import type { Locale } from './utils/index.js'
const sampleImage = fs.readFileSync( const sampleImage = fs.readFileSync(
path.join(__dirname, '../../compressor/fixtures/image.jpg'), path.join(__dirname, '../../compressor/fixtures/image.jpg'),

View file

@ -1,10 +1,28 @@
/* global AggregateError */ /* global AggregateError */
import DefaultStore, { type Store } from '@uppy/store-default' import throttle from 'lodash/throttle.js'
// @ts-expect-error untyped
import ee from 'namespace-emitter'
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,
Restricter,
RestrictionError,
} from './Restricter.js'
import DefaultStore, { type Store } from './store/index.js'
import supportsUploadProgress from './supportsUploadProgress.js'
import type { import type {
Body, Body,
CompanionClientProvider,
CompanionClientSearchProvider,
CompanionFile, CompanionFile,
FileProgressNotStarted, FileProgressNotStarted,
FileProgressStarted, FileProgressStarted,
@ -17,30 +35,13 @@ import type {
RemoteUppyFile, RemoteUppyFile,
UppyFile, UppyFile,
UppyFileId, UppyFileId,
} from '@uppy/utils' } from './utils/index.js'
import { import {
getFileNameAndExtension, getFileNameAndExtension,
getFileType, getFileType,
getSafeFileId, getSafeFileId,
Translator, Translator,
} from '@uppy/utils' } from './utils/index.js'
import throttle from 'lodash/throttle.js'
// @ts-expect-error untyped
import ee from 'namespace-emitter'
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 getFileName from './getFileName.js'
import locale from './locale.js'
import { debugLogger, justErrorsLogger } from './loggers.js'
import type { Restrictions, ValidateableFile } from './Restricter.js'
import {
defaultOptions as defaultRestrictionOptions,
Restricter,
RestrictionError,
} from './Restricter.js'
import supportsUploadProgress from './supportsUploadProgress.js'
type Processor = ( type Processor = (
fileIDs: string[], fileIDs: string[],
@ -165,8 +166,6 @@ export interface BaseProviderPlugin {
* UnknownProviderPlugin can be any Companion plugin (such as Google Drive) * UnknownProviderPlugin can be any Companion plugin (such as Google Drive)
* that uses the Companion-assisted OAuth flow. * that uses the Companion-assisted OAuth flow.
* As the plugins are passed around throughout Uppy we need a generic type for this. * 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/companion-client and vice versa.
* *
* Note that this is the *plugin* class, not a version of the `Provider` class. * Note that this is the *plugin* class, not a version of the `Provider` class.
* `Provider` does operate on Companion plugins with `uppy.getPlugin()`. * `Provider` does operate on Companion plugins with `uppy.getPlugin()`.
@ -178,16 +177,25 @@ export type UnknownProviderPlugin<
BaseProviderPlugin & { BaseProviderPlugin & {
rootFolderId: string | null rootFolderId: string | null
files: UppyFile<M, B>[] files: UppyFile<M, B>[]
provider: CompanionClientProvider // Structural subset of the real `Provider` class,structural rather than the nominal class type so custom
// Can't be typed unfortunately, we can't depend on `provider-views` in `core`. // providers matching the public surface aren't forced to inherit from `Provider`.
view: any 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). * 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. * 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/companion-client and vice versa.
* *
* Note that this is the *plugin* class, not a version of the `SearchProvider` class. * Note that this is the *plugin* class, not a version of the `SearchProvider` class.
* `SearchProvider` does operate on Companion plugins with `uppy.getPlugin()`. * `SearchProvider` does operate on Companion plugins with `uppy.getPlugin()`.
@ -203,7 +211,11 @@ export type UnknownSearchProviderPlugin<
B extends Body, B extends Body,
> = UnknownPlugin<M, B, UnknownSearchProviderPluginState> & > = UnknownPlugin<M, B, UnknownSearchProviderPluginState> &
BaseProviderPlugin & { 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 // for better readability

View file

@ -1,4 +1,4 @@
import type { AsyncStore, UIPluginOptions } from '@uppy/core' import type { AsyncStore, UIPluginOptions } from '../index.js'
export interface CompanionPluginOptions extends UIPluginOptions { export interface CompanionPluginOptions extends UIPluginOptions {
storage?: AsyncStore storage?: AsyncStore

View file

@ -4,14 +4,13 @@ import type {
PluginOpts, PluginOpts,
UnknownProviderPlugin, UnknownProviderPlugin,
Uppy, Uppy,
} from '@uppy/core' } from '../index.js'
import { import { getSocketHost } from '../utils/index.js'
type CompanionClientProvider,
getSocketHost,
type RequestOptions,
} from '@uppy/utils'
import type { CompanionPluginOptions } from './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 { export interface Opts extends PluginOpts, CompanionPluginOptions {
pluginId: string pluginId: string
@ -31,10 +30,10 @@ function getOrigin() {
return location.origin return location.origin
} }
export default class Provider<M extends Meta, B extends Body> export default class Provider<
extends RequestClient<M, B> M extends Meta,
implements CompanionClientProvider B extends Body,
{ > extends RequestClient<M, B> {
#refreshingTokenPromise: Promise<void> | undefined #refreshingTokenPromise: Promise<void> | undefined
provider: string provider: string
@ -273,11 +272,13 @@ export default class Provider<M extends Meta, B extends Body>
} }
async login({ async login({
uppyVersions, uppyVersions = '',
authFormData, authFormData,
signal, 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 authFormData: unknown
signal: AbortSignal signal: AbortSignal
}): Promise<void> { }): Promise<void> {

View file

@ -1,21 +1,24 @@
import type Uppy from '@uppy/core' import pRetry, { AbortError } from 'p-retry'
import type { import packageJson from '../../package.json' with { type: 'json' }
Body, import type Uppy from '../index.js'
Meta, import type { Body, Meta, RemoteUppyFile, UppyFile } from '../utils/index.js'
RemoteUppyFile,
RequestOptions,
UppyFile,
} from '@uppy/utils'
import { import {
ErrorWithCause, ErrorWithCause,
fetchWithNetworkError, fetchWithNetworkError,
getSocketHost, getSocketHost,
UserFacingApiError, UserFacingApiError,
} from '@uppy/utils' } from '../utils/index.js'
import pRetry, { AbortError } from 'p-retry'
import packageJson from '../package.json' with { type: 'json' }
import AuthError from './AuthError.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 type CompanionHeaders = Record<string, string> | undefined
export type Opts = { export type Opts = {

View file

@ -1,5 +1,4 @@
import type { Body, Meta, Uppy } from '@uppy/core' import type { Body, Meta, Uppy } from '../index.js'
import type { CompanionClientSearchProvider } from '@uppy/utils'
import RequestClient, { type Opts } from './RequestClient.js' import RequestClient, { type Opts } from './RequestClient.js'
const getName = (id: string): string => { const getName = (id: string): string => {
@ -9,10 +8,10 @@ const getName = (id: string): string => {
.join(' ') .join(' ')
} }
export default class SearchProvider<M extends Meta, B extends Body> export default class SearchProvider<
extends RequestClient<M, B> M extends Meta,
implements CompanionClientSearchProvider B extends Body,
{ > extends RequestClient<M, B> {
provider: string provider: string
id: string id: string

View file

@ -5,6 +5,9 @@
export type { CompanionPluginOptions } from './CompanionPluginOptions.js' export type { CompanionPluginOptions } from './CompanionPluginOptions.js'
export { default as getAllowedHosts } from './getAllowedHosts.js' export { default as getAllowedHosts } from './getAllowedHosts.js'
export { default as Provider } from './Provider.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 { default as SearchProvider } from './SearchProvider.js'
export * as tokenStorage from './tokenStorage.js' export * as tokenStorage from './tokenStorage.js'

View file

@ -1,16 +1,10 @@
export type { Store } from '@uppy/store-default'
export type {
Body,
Meta,
MinimalRequiredUppyFile,
UppyFile,
} from '@uppy/utils'
export type { DefinePluginOpts, PluginOpts } from './BasePlugin.js' export type { DefinePluginOpts, PluginOpts } from './BasePlugin.js'
export { default as BasePlugin } from './BasePlugin.js' export { default as BasePlugin } from './BasePlugin.js'
export { default as EventManager } from './EventManager.js' export { default as EventManager } from './EventManager.js'
export { debugLogger } from './loggers.js' export { debugLogger } from './loggers.js'
export type { Restrictions, ValidateableFile } from './Restricter.js' export type { Restrictions, ValidateableFile } from './Restricter.js'
export { RestrictionError } from './Restricter.js' export { RestrictionError } from './Restricter.js'
export type { Store } from './store/index.js'
export type { PluginTarget, UIPluginOptions } from './UIPlugin.js' export type { PluginTarget, UIPluginOptions } from './UIPlugin.js'
export { default as UIPlugin } from './UIPlugin.js' export { default as UIPlugin } from './UIPlugin.js'
export type { export type {
@ -34,3 +28,9 @@ export type {
UppyOptions, UppyOptions,
} from './Uppy.js' } from './Uppy.js'
export { default, default as Uppy } from './Uppy.js' export { default, default as Uppy } from './Uppy.js'
export type {
Body,
Meta,
MinimalRequiredUppyFile,
UppyFile,
} from './utils/index.js'

View file

@ -1,4 +1,4 @@
import { getTimeStamp } from '@uppy/utils' import { getTimeStamp } from './utils/index.js'
// Swallow all logs, except errors. // Swallow all logs, except errors.
// default if logger is not set or debug: false // default if logger is not set or debug: false

View file

@ -1,5 +1,5 @@
import type { Body, Meta, PartialTreeFolder } from '@uppy/core'
import { Fragment, type h } from 'preact' import { Fragment, type h } from 'preact'
import type { Body, Meta, PartialTreeFolder } from '../index.js'
import type ProviderView from './ProviderView/index.js' import type ProviderView from './ProviderView/index.js'
type BreadcrumbsProps<M extends Meta, B extends Body> = { type BreadcrumbsProps<M extends Meta, B extends Body> = {

View file

@ -1,12 +1,12 @@
import { useEffect, useState } from 'preact/hooks'
import type { import type {
Body, Body,
Meta, Meta,
PartialTreeFile, PartialTreeFile,
PartialTreeFolderNode, PartialTreeFolderNode,
} from '@uppy/core' } from '../index.js'
import type { I18n } from '@uppy/utils' import type { I18n } from '../utils/index.js'
import { VirtualList } from '@uppy/utils' import { VirtualList } from '../utils/index.js'
import { useEffect, useState } from 'preact/hooks'
import Item from './Item/index.js' import Item from './Item/index.js'
import type ProviderView from './ProviderView/ProviderView.js' import type ProviderView from './ProviderView/ProviderView.js'

View file

@ -1,4 +1,4 @@
import type { I18n } from '@uppy/utils' import type { I18n } from '../utils/index.js'
import { useSearchForm } from './useSearchForm.js' import { useSearchForm } from './useSearchForm.js'
interface FilterInputProps { interface FilterInputProps {

View file

@ -1,7 +1,7 @@
import type { Body, Meta, PartialTree } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import classNames from 'classnames' import classNames from 'classnames'
import { useMemo } from 'preact/hooks' import { useMemo } from 'preact/hooks'
import type { Body, Meta, PartialTree } from '../index.js'
import type { I18n } from '../utils/index.js'
import type ProviderView from './ProviderView/ProviderView.js' import type ProviderView from './ProviderView/ProviderView.js'
import getNumberOfSelectedFiles from './utils/PartialTreeUtils/getNumberOfSelectedFiles.js' import getNumberOfSelectedFiles from './utils/PartialTreeUtils/getNumberOfSelectedFiles.js'

View file

@ -1,6 +1,6 @@
import type { AsyncStore, Uppy } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks' import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import type { AsyncStore, Uppy } from '../../index.js'
import type { I18n } from '../../utils/index.js'
import AuthView from '../ProviderView/AuthView.js' import AuthView from '../ProviderView/AuthView.js'
import { import {
authorize, authorize,

View file

@ -1,5 +1,5 @@
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core'
import type { h } from 'preact' import type { h } from 'preact'
import type { PartialTreeFile, PartialTreeFolderNode } from '../../../index.js'
import ItemIcon from './ItemIcon.js' import ItemIcon from './ItemIcon.js'
type GridItemProps = { type GridItemProps = {

View file

@ -1,9 +1,9 @@
import type { h } from 'preact'
import type { import type {
PartialTreeFile, PartialTreeFile,
PartialTreeFolderNode, PartialTreeFolderNode,
PartialTreeId, PartialTreeId,
} from '@uppy/core' } from '../../../index.js'
import type { h } from 'preact'
import ItemIcon from './ItemIcon.js' import ItemIcon from './ItemIcon.js'
// if folder: // if folder:

View file

@ -1,6 +1,6 @@
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import classNames from 'classnames' import classNames from 'classnames'
import type { PartialTreeFile, PartialTreeFolderNode } from '../../../index.js'
import type { I18n } from '../../../utils/index.js'
import type ProviderView from '../../ProviderView/ProviderView.js' import type ProviderView from '../../ProviderView/ProviderView.js'
import ItemIcon from './ItemIcon.js' import ItemIcon from './ItemIcon.js'

View file

@ -1,11 +1,11 @@
import classNames from 'classnames'
import type { h } from 'preact'
import type { import type {
PartialTreeFile, PartialTreeFile,
PartialTreeFolderNode, PartialTreeFolderNode,
PartialTreeId, PartialTreeId,
} from '@uppy/core' } from '../../index.js'
import type { I18n } from '@uppy/utils' import type { I18n } from '../../utils/index.js'
import classNames from 'classnames'
import type { h } from 'preact'
import GridItem from './components/GridItem.js' import GridItem from './components/GridItem.js'
import ListItem from './components/ListItem.js' import ListItem from './components/ListItem.js'

View file

@ -1,7 +1,7 @@
import type { Body, Meta } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import type { h } from 'preact' import type { h } from 'preact'
import { useCallback } from 'preact/hooks' import { useCallback } from 'preact/hooks'
import type { Body, Meta } from '../../index.js'
import type { I18n } from '../../utils/index.js'
import type ProviderViews from './ProviderView.js' import type ProviderViews from './ProviderView.js'
import type { Opts } from './ProviderView.js' import type { Opts } from './ProviderView.js'

View file

@ -1,5 +1,5 @@
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core' import type { PartialTreeFile, PartialTreeFolderNode } from '../../index.js'
import type { I18n } from '@uppy/utils' import type { I18n } from '../../utils/index.js'
import SearchResultItem from '../Item/components/SearchResultItem.js' import SearchResultItem from '../Item/components/SearchResultItem.js'
import type ProviderView from './ProviderView.js' import type ProviderView from './ProviderView.js'

View file

@ -1,7 +1,7 @@
import type { Body, Meta, PartialTreeFolder } from '@uppy/core'
import type { I18n } from '@uppy/utils'
import classNames from 'classnames' import classNames from 'classnames'
import type { h } from 'preact' import type { h } from 'preact'
import type { Body, Meta, PartialTreeFolder } from '../../index.js'
import type { I18n } from '../../utils/index.js'
import Breadcrumbs from '../Breadcrumbs.js' import Breadcrumbs from '../Breadcrumbs.js'
import type ProviderView from './ProviderView.js' import type ProviderView from './ProviderView.js'
import User from './User.js' import User from './User.js'

View file

@ -1,3 +1,7 @@
import classNames from 'classnames'
import debounce from 'lodash/debounce.js'
import type { h } from 'preact'
import packageJson from '../../../package.json' with { type: 'json' }
import type { import type {
Body, Body,
Meta, Meta,
@ -9,13 +13,9 @@ import type {
UnknownProviderPlugin, UnknownProviderPlugin,
UnknownProviderPluginState, UnknownProviderPluginState,
ValidateableFile, ValidateableFile,
} from '@uppy/core' } from '../../index.js'
import type { CompanionFile, I18n } from '@uppy/utils' import type { CompanionFile, I18n } from '../../utils/index.js'
import { remoteFileObjToLocal } from '@uppy/utils' import { remoteFileObjToLocal } from '../../utils/index.js'
import classNames from 'classnames'
import debounce from 'lodash/debounce.js'
import type { h } from 'preact'
import packageJson from '../../package.json' with { type: 'json' }
import Browser from '../Browser.js' import Browser from '../Browser.js'
import FilterInput from '../FilterInput.js' import FilterInput from '../FilterInput.js'
import FooterActions from '../FooterActions.js' import FooterActions from '../FooterActions.js'
@ -66,6 +66,17 @@ const getDefaultState = (
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K> 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> { export interface Opts<M extends Meta, B extends Body> {
provider: UnknownProviderPlugin<M, B>['provider'] provider: UnknownProviderPlugin<M, B>['provider']
viewType: 'list' | 'grid' viewType: 'list' | 'grid'
@ -248,10 +259,13 @@ export default class ProviderView<M extends Meta, B extends Body> {
await this.#withAbort(async (signal) => { await this.#withAbort(async (signal) => {
const scopePath = const scopePath =
currentFolder.type === 'root' ? undefined : currentFolderId currentFolder.type === 'root' ? undefined : currentFolderId
const { items } = await this.provider.search!(searchString, { const { items } = await this.provider.search<ProviderListResponse>(
signal, searchString,
path: scopePath, {
}) signal,
path: scopePath,
},
)
// For each searched file, build the entire path (from the root all the way to the leaf node) // 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. // 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 currentPagePath = folderId
let currentItems: CompanionFile[] = [] let currentItems: CompanionFile[] = []
do { do {
const { username, nextPagePath, items } = await this.provider.list( const { username, nextPagePath, items } =
currentPagePath, await this.provider.list<ProviderListResponse>(currentPagePath, {
{ signal }, signal,
) })
// It's important to set the username during one of our first fetches // It's important to set the username during one of our first fetches
this.plugin.setPluginState({ username }) this.plugin.setPluginState({ username })
@ -478,10 +492,11 @@ export default class ProviderView<M extends Meta, B extends Body> {
) { ) {
this.isHandlingScroll = true this.isHandlingScroll = true
await this.#withAbort(async (signal) => { await this.#withAbort(async (signal) => {
const { nextPagePath, items } = await this.provider.list( const { nextPagePath, items } =
currentFolder.nextPagePath, await this.provider.list<ProviderListResponse>(
{ signal }, currentFolder.nextPagePath,
) { signal },
)
const newPartialTree = PartialTreeUtils.afterScrollFolder( const newPartialTree = PartialTreeUtils.afterScrollFolder(
partialTree, partialTree,
currentFolderId, currentFolderId,

View file

@ -1,3 +1,6 @@
import classNames from 'classnames'
import type { h } from 'preact'
import packageJson from '../../../package.json' with { type: 'json' }
import type { import type {
Body, Body,
DefinePluginOpts, DefinePluginOpts,
@ -9,12 +12,9 @@ import type {
UnknownSearchProviderPlugin, UnknownSearchProviderPlugin,
UnknownSearchProviderPluginState, UnknownSearchProviderPluginState,
ValidateableFile, ValidateableFile,
} from '@uppy/core' } from '../../index.js'
import type { CompanionFile } from '@uppy/utils' import type { CompanionFile } from '../../utils/index.js'
import { remoteFileObjToLocal } from '@uppy/utils' import { remoteFileObjToLocal } from '../../utils/index.js'
import classNames from 'classnames'
import type { h } from 'preact'
import packageJson from '../../package.json' with { type: 'json' }
import Browser from '../Browser.js' import Browser from '../Browser.js'
import FilterInput from '../FilterInput.js' import FilterInput from '../FilterInput.js'
import FooterActions from '../FooterActions.js' import FooterActions from '../FooterActions.js'

View file

@ -1,13 +1,13 @@
// p-queue does not have a `"main"` field in its `package.json`, and that makes `import/no-unresolved` freak out.
// We can safely ignore it because bundlers will happily use the `"exports"` field instead.
import PQueue from 'p-queue'
import type { import type {
PartialTree, PartialTree,
PartialTreeFile, PartialTreeFile,
PartialTreeFolderNode, PartialTreeFolderNode,
PartialTreeId, PartialTreeId,
} from '@uppy/core' } from '../../../index.js'
import type { CompanionFile } from '@uppy/utils' import type { CompanionFile } from '../../../utils/index.js'
// p-queue does not have a `"main"` field in its `package.json`, and that makes `import/no-unresolved` freak out.
// We can safely ignore it because bundlers will happily use the `"exports"` field instead.
import PQueue from 'p-queue'
import shallowClone from './shallowClone.js' import shallowClone from './shallowClone.js'
export type ApiList = (directory: PartialTreeId) => Promise<{ export type ApiList = (directory: PartialTreeId) => Promise<{

View file

@ -3,8 +3,8 @@ import type {
PartialTreeFile, PartialTreeFile,
PartialTreeFolder, PartialTreeFolder,
PartialTreeFolderNode, PartialTreeFolderNode,
} from '@uppy/core' } from '../../../index.js'
import type { CompanionFile } from '@uppy/utils' import type { CompanionFile } from '../../../utils/index.js'
const afterOpenFolder = ( const afterOpenFolder = (
oldPartialTree: PartialTree, oldPartialTree: PartialTree,

View file

@ -4,8 +4,8 @@ import type {
PartialTreeFolder, PartialTreeFolder,
PartialTreeFolderNode, PartialTreeFolderNode,
PartialTreeId, PartialTreeId,
} from '@uppy/core' } from '../../../index.js'
import type { CompanionFile } from '@uppy/utils' import type { CompanionFile } from '../../../utils/index.js'
const afterScrollFolder = ( const afterScrollFolder = (
oldPartialTree: PartialTree, oldPartialTree: PartialTree,

View file

@ -4,7 +4,7 @@ import type {
PartialTreeFolder, PartialTreeFolder,
PartialTreeFolderNode, PartialTreeFolderNode,
PartialTreeId, PartialTreeId,
} from '@uppy/core' } from '../../../index.js'
import shallowClone from './shallowClone.js' import shallowClone from './shallowClone.js'
/* /*

View file

@ -3,7 +3,7 @@ import type {
PartialTreeFolder, PartialTreeFolder,
PartialTreeFolderNode, PartialTreeFolderNode,
PartialTreeId, PartialTreeId,
} from '@uppy/core' } from '../../../index.js'
const getBreadcrumbs = ( const getBreadcrumbs = (
partialTree: PartialTree, partialTree: PartialTree,

View file

@ -3,8 +3,8 @@ import type {
PartialTreeFile, PartialTreeFile,
PartialTreeFolderNode, PartialTreeFolderNode,
PartialTreeId, PartialTreeId,
} from '@uppy/core' } from '../../../index.js'
import type { CompanionFile } from '@uppy/utils' import type { CompanionFile } from '../../../utils/index.js'
export interface Cache { export interface Cache {
[key: string]: (PartialTreeFile | PartialTreeFolderNode)[] [key: string]: (PartialTreeFile | PartialTreeFolderNode)[]

View file

@ -1,4 +1,4 @@
import type { PartialTree } from '@uppy/core' import type { PartialTree } from '../../../index.js'
/** /**
* We're interested in all 'checked' leaves of this tree, * We're interested in all 'checked' leaves of this tree,

View file

@ -1,12 +1,12 @@
import { describe, expect, it, vi } from 'vitest'
import type { import type {
PartialTree, PartialTree,
PartialTreeFile, PartialTreeFile,
PartialTreeFolderNode, PartialTreeFolderNode,
PartialTreeFolderRoot, PartialTreeFolderRoot,
PartialTreeId, PartialTreeId,
} from '@uppy/core' } from '../../../index.js'
import type { CompanionFile } from '@uppy/utils' import type { CompanionFile } from '../../../utils/index.js'
import { describe, expect, it, vi } from 'vitest'
import afterFill from './afterFill.js' import afterFill from './afterFill.js'
import afterOpenFolder from './afterOpenFolder.js' import afterOpenFolder from './afterOpenFolder.js'
import afterScrollFolder from './afterScrollFolder.js' import afterScrollFolder from './afterScrollFolder.js'

View file

@ -1,4 +1,4 @@
import type { PartialTree } from '@uppy/core' import type { PartialTree } from '../../../index.js'
/** /**
* One-level copying is sufficient as mutations within our `partialTree` are limited to properties * One-level copying is sufficient as mutations within our `partialTree` are limited to properties

View file

@ -1,19 +1,23 @@
import type { UnknownPlugin } from '@uppy/core' import type {
UnknownPlugin,
UnknownProviderPlugin,
UnknownSearchProviderPlugin,
} from '../../index.js'
import type { import type {
Body, Body,
CompanionClientProvider,
CompanionClientSearchProvider,
CompanionFile, CompanionFile,
Meta, Meta,
UppyFileNonGhost, UppyFileNonGhost,
} from '@uppy/utils' } from '../../utils/index.js'
import { getSafeFileId } from '@uppy/utils' import { getSafeFileId } from '../../utils/index.js'
import companionFileToUppyFile from './companionFileToUppyFile.js' import companionFileToUppyFile from './companionFileToUppyFile.js'
const addFiles = <M extends Meta, B extends Body>( const addFiles = <M extends Meta, B extends Body>(
companionFiles: CompanionFile[], companionFiles: CompanionFile[],
plugin: UnknownPlugin<M, B>, plugin: UnknownPlugin<M, B>,
provider: CompanionClientProvider | CompanionClientSearchProvider, provider:
| UnknownProviderPlugin<M, B>['provider']
| UnknownSearchProviderPlugin<M, B>['provider'],
): void => { ): void => {
const uppyFiles = companionFiles.map((f) => const uppyFiles = companionFiles.map((f) =>
companionFileToUppyFile<M, B>(f, plugin, provider), companionFileToUppyFile<M, B>(f, plugin, provider),

View file

@ -1,17 +1,21 @@
import type { UnknownPlugin } from '@uppy/core' import type {
UnknownPlugin,
UnknownProviderPlugin,
UnknownSearchProviderPlugin,
} from '../../index.js'
import type { import type {
Body, Body,
CompanionClientProvider,
CompanionClientSearchProvider,
CompanionFile, CompanionFile,
Meta, Meta,
RemoteUppyFile, RemoteUppyFile,
} from '@uppy/utils' } from '../../utils/index.js'
const companionFileToUppyFile = <M extends Meta, B extends Body>( const companionFileToUppyFile = <M extends Meta, B extends Body>(
file: CompanionFile, file: CompanionFile,
plugin: UnknownPlugin<M, B>, plugin: UnknownPlugin<M, B>,
provider: CompanionClientProvider | CompanionClientSearchProvider, provider:
| UnknownProviderPlugin<M, B>['provider']
| UnknownSearchProviderPlugin<M, B>['provider'],
): RemoteUppyFile<M, B> => { ): RemoteUppyFile<M, B> => {
const name = file.name || file.id const name = file.name || file.id

View file

@ -1,4 +1,4 @@
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core' import type { PartialTreeFile, PartialTreeFolderNode } from '../../index.js'
// Shift-clicking selects a single consecutive list of items // Shift-clicking selects a single consecutive list of items
// starting at the previous click. // starting at the previous click.

Some files were not shown because too many files have changed in this diff Show more