mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-30 13:30:31 +00:00
Merge branch 'main' into ai-image-generator
* main: [ci] release (#5997) Use assembly_ssl_url for final status fetch (#5996) [ci] release (#5988) @uppy/svelte: fix props reactivity (#5991) Improve changeset again Improve a changeset fixup! Use workspace:* for all packages in packages/uppy Use workspace:* for all packages in packages/uppy Re-use types from the Transloadit node-sdk (#5992)
This commit is contained in:
commit
ff7bc6a095
21 changed files with 1729 additions and 278 deletions
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@uppy/components": patch
|
||||
---
|
||||
|
||||
add imageThumbnail prop to FilesList/FilesGrid
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Hi, thanks for trying out the bundled version of the Uppy File Uploader. You can
|
||||
use this from a CDN
|
||||
(`<script src="https://releases.transloadit.com/uppy/v5.1.2/uppy.min.js"></script>`)
|
||||
(`<script src="https://releases.transloadit.com/uppy/v5.1.4/uppy.min.js"></script>`)
|
||||
or bundle it with your webapp.
|
||||
|
||||
Note that the recommended way to use Uppy is to install it with yarn/npm and use
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ npm install @uppy/core @uppy/dashboard @uppy/tus
|
|||
```
|
||||
|
||||
Add CSS
|
||||
[uppy.min.css](https://releases.transloadit.com/uppy/v5.1.2/uppy.min.css),
|
||||
[uppy.min.css](https://releases.transloadit.com/uppy/v5.1.4/uppy.min.css),
|
||||
either to your HTML page’s `<head>` or include in JS, if your bundler of choice
|
||||
supports it.
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ CDN. In that case `Uppy` will attach itself to the global `window.Uppy` object.
|
|||
```html
|
||||
<!-- 1. Add CSS to `<head>` -->
|
||||
<link
|
||||
href="https://releases.transloadit.com/uppy/v5.1.2/uppy.min.css"
|
||||
href="https://releases.transloadit.com/uppy/v5.1.4/uppy.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ CDN. In that case `Uppy` will attach itself to the global `window.Uppy` object.
|
|||
Uppy,
|
||||
Dashboard,
|
||||
Tus,
|
||||
} from 'https://releases.transloadit.com/uppy/v5.1.2/uppy.min.mjs'
|
||||
} from 'https://releases.transloadit.com/uppy/v5.1.4/uppy.min.mjs'
|
||||
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { target: '#files-drag-drop' })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import Uppy from '@uppy/core'
|
||||
import Dashboard from '@uppy/svelte/dashboard'
|
||||
import StatusBar from '@uppy/svelte/status-bar'
|
||||
|
||||
import '@uppy/core/css/style.css'
|
||||
import '@uppy/dashboard/css/style.css'
|
||||
import '@uppy/status-bar/css/style.css'
|
||||
|
||||
const uppy = new Uppy()
|
||||
uppy.addFile(new File(['hello world'], 'hello.txt'))
|
||||
let disabled = false
|
||||
let hideUploadButton = false
|
||||
</script>
|
||||
|
||||
<main class="p-5 max-w-xl mx-auto">
|
||||
<button onclick={() => (disabled = !disabled)}>Toggle dashboard</button>
|
||||
<Dashboard props={{disabled: disabled}} uppy={uppy} />
|
||||
<button onclick={() => (hideUploadButton = !hideUploadButton)}>Toggle statusbar</button>
|
||||
<div id="statusbar-container">
|
||||
<StatusBar props={{ hideUploadButton }} uppy={uppy} />
|
||||
</div>
|
||||
</main>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { userEvent } from '@vitest/browser/context'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { render } from 'vitest-browser-svelte'
|
||||
import PropsReactivity from '../src/components/test/props-reactivity.svelte'
|
||||
import App from '../src/routes/+page.svelte'
|
||||
|
||||
const createMockFile = (name: string, type: string, size: number = 1024) => {
|
||||
|
|
@ -124,3 +125,27 @@ describe('RemoteSource Component', () => {
|
|||
await expect.element(loginButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test('Dashboard reacts to prop changes', async () => {
|
||||
const screen = render(PropsReactivity)
|
||||
const toggleButton = screen.getByText('Toggle dashboard')
|
||||
const dashboard = screen.container.querySelector('.uppy-Dashboard')
|
||||
|
||||
expect(dashboard).toBeTruthy()
|
||||
expect(dashboard?.ariaDisabled).toEqual('false')
|
||||
await userEvent.click(toggleButton)
|
||||
expect(dashboard?.ariaDisabled).toEqual('true')
|
||||
})
|
||||
|
||||
test('StatusBar reacts to prop changes', async () => {
|
||||
const screen = render(PropsReactivity)
|
||||
const toggleButton = screen.getByText('Toggle statusbar')
|
||||
|
||||
expect(
|
||||
screen.container.querySelector('#statusbar-container .uppy-c-btn-primary'),
|
||||
).toBeVisible()
|
||||
await userEvent.click(toggleButton)
|
||||
expect(
|
||||
screen.container.querySelector('#statusbar-container .uppy-c-btn-primary'),
|
||||
).toEqual(null)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# @uppy/components
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 34639ba: add imageThumbnail prop to FilesList/FilesGrid
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@uppy/components",
|
||||
"description": "Headless Uppy components, made in Preact",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# @uppy/svelte
|
||||
|
||||
## 5.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- da754b7: Fix props reactivity. Now when the value of a prop you pass to a component changes, it is actually picked up.
|
||||
- Updated dependencies [34639ba]
|
||||
- @uppy/components@1.0.3
|
||||
|
||||
## 5.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@uppy/svelte",
|
||||
"version": "5.0.1",
|
||||
"version": "5.0.2",
|
||||
"description": "Uppy plugin that helps integrate Uppy into your Svelte project.",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ $: {
|
|||
...props,
|
||||
target: container,
|
||||
} satisfies DashboardOptions<M, B>;
|
||||
uppy.setOptions(options);
|
||||
plugin?.setOptions(options);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ $: {
|
|||
...props,
|
||||
target: container,
|
||||
} satisfies DashboardOptions<M, B>;
|
||||
uppy.setOptions(options);
|
||||
plugin?.setOptions(options);
|
||||
}
|
||||
$: {
|
||||
if (open && !lastOpen) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ $: {
|
|||
...props,
|
||||
target: container,
|
||||
} satisfies StatusBarOptions;
|
||||
uppy.setOptions(options);
|
||||
plugin?.setOptions(options);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,24 @@
|
|||
# @uppy/transloadit
|
||||
|
||||
## 5.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- bc2d0ed: Ensure final assembly status fetch uses `assembly_ssl_url` so Transloadit requests stay on HTTPS.
|
||||
|
||||
## 5.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 6f76412: Use the `transloadit` Node.js SDK's exported Assembly types instead of our inaccurate, hand-rolled ones.
|
||||
|
||||
**Warning**
|
||||
|
||||
The names of our type exports here are unchanged, but they do pack slightly different types. Overall you'll find they are both more complete, but also more loose. Runtime wise there should be no breaking changes, but it could mean you may need a couple of extra guards to make TypeScript happy.
|
||||
|
||||
A cool benefit from the new types tho, is that Robot parameters will now autocomplete for you.
|
||||
More information on these types, and our approach rolling them out, can be found here https://transloadit.com/blog/2025/09/nodejs-sdk-v4/#our-approach-to-type-retrofitting
|
||||
|
||||
## 5.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@uppy/transloadit",
|
||||
"description": "The Transloadit plugin can be used to upload files to Transloadit for all kinds of processing, such as transcoding video, resizing images, zipping/unzipping, and more",
|
||||
"version": "5.0.1",
|
||||
"version": "5.1.1",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
"@uppy/core": "workspace:^",
|
||||
"jsdom": "^26.1.0",
|
||||
"msw": "^2.10.4",
|
||||
"transloadit": "^4.0.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ import {
|
|||
NetworkError,
|
||||
} from '@uppy/utils'
|
||||
import Emitter from 'component-emitter'
|
||||
import type { AssemblyResponse } from './index.js'
|
||||
import {
|
||||
type AssemblyFile,
|
||||
type AssemblyResponse,
|
||||
type AssemblyResult,
|
||||
getAssemblyUrlSsl,
|
||||
} from './index.js'
|
||||
|
||||
const ASSEMBLY_UPLOADING = 'ASSEMBLY_UPLOADING'
|
||||
const ASSEMBLY_EXECUTING = 'ASSEMBLY_EXECUTING'
|
||||
|
|
@ -24,7 +29,11 @@ const statusOrder = [ASSEMBLY_UPLOADING, ASSEMBLY_EXECUTING, ASSEMBLY_COMPLETED]
|
|||
* …so that we can emit the 'executing' event even if the execution step was so
|
||||
* fast that we missed it.
|
||||
*/
|
||||
function isStatus(status: string, test: string) {
|
||||
function isStatus(status: unknown, test: string) {
|
||||
if (typeof status !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
return statusOrder.indexOf(status) >= statusOrder.indexOf(test)
|
||||
}
|
||||
|
||||
|
|
@ -101,16 +110,22 @@ class TransloaditAssembly extends Emitter {
|
|||
})
|
||||
|
||||
this.#sse.addEventListener('assembly_upload_finished', (e) => {
|
||||
const file = JSON.parse(e.data)
|
||||
const file = JSON.parse(e.data) as AssemblyFile
|
||||
this.status.uploads ??= []
|
||||
this.status.uploads.push(file)
|
||||
this.emit('upload', file)
|
||||
})
|
||||
|
||||
this.#sse.addEventListener('assembly_result_finished', (e) => {
|
||||
const [stepName, result] = JSON.parse(e.data)
|
||||
// biome-ignore lint/suspicious/noAssignInExpressions: ...
|
||||
;(this.status.results[stepName] ??= []).push(result)
|
||||
this.emit('result', stepName, result)
|
||||
const [stepName, rawResult] = JSON.parse(e.data) as [
|
||||
string,
|
||||
AssemblyResult,
|
||||
]
|
||||
|
||||
this.status.results ??= {}
|
||||
this.status.results[stepName] ??= []
|
||||
this.status.results[stepName].push(rawResult)
|
||||
this.emit('result', stepName, rawResult)
|
||||
})
|
||||
|
||||
this.#sse.addEventListener('assembly_execution_progress', (e) => {
|
||||
|
|
@ -165,9 +180,9 @@ class TransloaditAssembly extends Emitter {
|
|||
|
||||
try {
|
||||
this.#previousFetchStatusStillPending = true
|
||||
const response = await this.#fetchWithNetworkError(
|
||||
this.status.assembly_ssl_url,
|
||||
)
|
||||
const statusUrl = getAssemblyUrlSsl(this.status)
|
||||
|
||||
const response = await this.#fetchWithNetworkError(statusUrl)
|
||||
this.#previousFetchStatusStillPending = false
|
||||
|
||||
if (this.closed) return
|
||||
|
|
@ -244,29 +259,38 @@ class TransloaditAssembly extends Emitter {
|
|||
}
|
||||
|
||||
// Only emit if the upload is new (not in prev.uploads).
|
||||
Object.keys(next.uploads)
|
||||
.filter((upload) => !has(prev.uploads, upload))
|
||||
.forEach((upload) => {
|
||||
// @ts-ignore either the types are wrong or the tests are wrong.
|
||||
// types think next.uploads is an array, but the tests pass an object.
|
||||
this.emit('upload', next.uploads[upload])
|
||||
})
|
||||
const prevUploads = prev.uploads
|
||||
const nextUploads = next.uploads
|
||||
if (nextUploads != null && prevUploads != null) {
|
||||
Object.keys(nextUploads)
|
||||
.filter((upload) => !has(prevUploads, upload))
|
||||
.forEach((upload) => {
|
||||
// This is a bit confusing. Not sure why Object.keys was chosen here, because nextUploads is an Array. Object.keys returns strings for array keys ("0", "1", etc.). Typescript expects arrays to be indexed with a number, not a string nextUploads[0], even though JavaScript is fine with it, so we need to type assert here:
|
||||
this.emit('upload', nextUploads[upload as unknown as number])
|
||||
})
|
||||
}
|
||||
|
||||
if (nowExecuting) {
|
||||
this.emit('metadata')
|
||||
}
|
||||
|
||||
// Find new results.
|
||||
Object.keys(next.results).forEach((stepName) => {
|
||||
const nextResults = next.results[stepName]
|
||||
const prevResults = prev.results[stepName]
|
||||
const nextResultsMap = next.results
|
||||
const prevResultsMap = prev.results
|
||||
if (nextResultsMap != null && prevResultsMap != null) {
|
||||
Object.keys(nextResultsMap).forEach((stepName) => {
|
||||
const nextResults = nextResultsMap[stepName] ?? []
|
||||
const prevResults = prevResultsMap[stepName] ?? []
|
||||
|
||||
nextResults
|
||||
.filter((n) => !prevResults || !prevResults.some((p) => p.id === n.id))
|
||||
.forEach((result) => {
|
||||
this.emit('result', stepName, result)
|
||||
})
|
||||
})
|
||||
nextResults
|
||||
.filter(
|
||||
(n) => !prevResults || !prevResults.some((p) => p.id === n.id),
|
||||
)
|
||||
.forEach((result) => {
|
||||
this.emit('result', stepName, result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
isStatus(nextStatus, ASSEMBLY_COMPLETED) &&
|
||||
|
|
|
|||
|
|
@ -49,21 +49,23 @@ class TransloaditAssemblyWatcher<
|
|||
}
|
||||
|
||||
#onAssemblyComplete = (assembly: AssemblyResponse) => {
|
||||
if (!this.#watching(assembly.assembly_id)) {
|
||||
const assemblyId = assembly.assembly_id
|
||||
if (assemblyId == null || !this.#watching(assemblyId)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.#uppy.log(
|
||||
`[Transloadit] AssemblyWatcher: Got Assembly finish ${assembly.assembly_id}`,
|
||||
`[Transloadit] AssemblyWatcher: Got Assembly finish ${assemblyId}`,
|
||||
)
|
||||
|
||||
this.emit('assembly-complete', assembly.assembly_id)
|
||||
this.emit('assembly-complete', assemblyId)
|
||||
|
||||
this.#checkAllComplete()
|
||||
}
|
||||
|
||||
#onAssemblyCancel = (assembly: AssemblyResponse) => {
|
||||
if (!this.#watching(assembly.assembly_id)) {
|
||||
const assemblyId = assembly.assembly_id
|
||||
if (assemblyId == null || !this.#watching(assemblyId)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -71,16 +73,17 @@ class TransloaditAssemblyWatcher<
|
|||
}
|
||||
|
||||
#onAssemblyError = (assembly: AssemblyResponse, error: Error) => {
|
||||
if (!this.#watching(assembly.assembly_id)) {
|
||||
const assemblyId = assembly.assembly_id
|
||||
if (assemblyId == null || !this.#watching(assemblyId)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.#uppy.log(
|
||||
`[Transloadit] AssemblyWatcher: Got Assembly error ${assembly.assembly_id}`,
|
||||
`[Transloadit] AssemblyWatcher: Got Assembly error ${assemblyId}`,
|
||||
)
|
||||
this.#uppy.log(error)
|
||||
|
||||
this.emit('assembly-error', assembly.assembly_id, error)
|
||||
this.emit('assembly-error', assemblyId, error)
|
||||
|
||||
this.#checkAllComplete()
|
||||
}
|
||||
|
|
@ -90,7 +93,8 @@ class TransloaditAssemblyWatcher<
|
|||
fileID: string,
|
||||
error: Error,
|
||||
) => {
|
||||
if (!this.#watching(assembly.assembly_id)) {
|
||||
const assemblyId = assembly.assembly_id
|
||||
if (assemblyId == null || !this.#watching(assemblyId)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import type {
|
|||
WrapPromiseFunctionType,
|
||||
} from '@uppy/utils'
|
||||
import { fetchWithNetworkError } from '@uppy/utils'
|
||||
import type {
|
||||
AssemblyResponse,
|
||||
OptionsWithRestructuredFields,
|
||||
import {
|
||||
type AssemblyResponse,
|
||||
getAssemblyUrlSsl,
|
||||
type OptionsWithRestructuredFields,
|
||||
} from './index.js'
|
||||
|
||||
const ASSEMBLIES_ENDPOINT = '/assemblies'
|
||||
|
|
@ -139,7 +140,8 @@ export default class Client<M extends Meta, B extends Body> {
|
|||
file: UppyFile<M, B>,
|
||||
): Promise<AssemblyResponse> {
|
||||
const size = encodeURIComponent(file.size!)
|
||||
const url = `${assembly.assembly_ssl_url}/reserve_file?size=${size}`
|
||||
const assemblyUrl = getAssemblyUrlSsl(assembly)
|
||||
const url = `${assemblyUrl}/reserve_file?size=${size}`
|
||||
return this.#fetchJSON(url, {
|
||||
method: 'POST',
|
||||
headers: this.#headers,
|
||||
|
|
@ -164,7 +166,8 @@ export default class Client<M extends Meta, B extends Body> {
|
|||
const fieldname = 'file'
|
||||
|
||||
const qs = `size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${uploadUrl}`
|
||||
const url = `${assembly.assembly_ssl_url}/add_file?${qs}`
|
||||
const assemblyUrl = getAssemblyUrlSsl(assembly)
|
||||
const url = `${assemblyUrl}/add_file?${qs}`
|
||||
return this.#fetchJSON(url, {
|
||||
method: 'POST',
|
||||
headers: this.#headers,
|
||||
|
|
@ -177,7 +180,7 @@ export default class Client<M extends Meta, B extends Body> {
|
|||
* Cancel a running Assembly.
|
||||
*/
|
||||
async cancelAssembly(assembly: AssemblyResponse): Promise<void> {
|
||||
const url = assembly.assembly_ssl_url
|
||||
const url = getAssemblyUrlSsl(assembly)
|
||||
await this.#fetchWithNetworkError(url, {
|
||||
method: 'DELETE',
|
||||
headers: this.#headers,
|
||||
|
|
|
|||
|
|
@ -9,111 +9,25 @@ import type {
|
|||
import { BasePlugin } from '@uppy/core'
|
||||
import Tus, { type TusDetailedError, type TusOpts } from '@uppy/tus'
|
||||
import { ErrorWithCause, hasProperty, RateLimitedQueue } from '@uppy/utils'
|
||||
import type {
|
||||
AssemblyStatus,
|
||||
AssemblyStatusResult,
|
||||
AssemblyStatusUpload,
|
||||
CreateAssemblyParams,
|
||||
} from 'transloadit'
|
||||
import packageJson from '../package.json' with { type: 'json' }
|
||||
import Assembly from './Assembly.js'
|
||||
import AssemblyWatcher from './AssemblyWatcher.js'
|
||||
import Client, { type AssemblyError } from './Client.js'
|
||||
import locale from './locale.js'
|
||||
|
||||
export interface AssemblyFile {
|
||||
id: string
|
||||
name: string
|
||||
basename: string
|
||||
ext: string
|
||||
size: number
|
||||
mime: string
|
||||
type: string
|
||||
field: string
|
||||
md5hash: string
|
||||
is_tus_file: boolean
|
||||
original_md5hash: string
|
||||
original_id: string
|
||||
original_name: string
|
||||
original_basename: string
|
||||
original_path: string
|
||||
url: string
|
||||
ssl_url: string
|
||||
tus_upload_url: string
|
||||
meta: Record<string, any>
|
||||
}
|
||||
|
||||
export interface AssemblyResult extends AssemblyFile {
|
||||
cost: number
|
||||
execTime: number
|
||||
queue: string
|
||||
queueTime: number
|
||||
localId: string | null
|
||||
user_meta?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface AssemblyResponse {
|
||||
ok: string
|
||||
message?: string
|
||||
assembly_id: string
|
||||
parent_id?: string
|
||||
account_id: string
|
||||
template_id?: string
|
||||
instance: string
|
||||
assembly_url: string
|
||||
assembly_ssl_url: string
|
||||
uppyserver_url: string
|
||||
companion_url: string
|
||||
websocket_url: string
|
||||
tus_url: string
|
||||
bytes_received: number
|
||||
bytes_expected: number
|
||||
upload_duration: number
|
||||
client_agent?: string
|
||||
client_ip?: string
|
||||
client_referer?: string
|
||||
transloadit_client: string
|
||||
start_date: string
|
||||
upload_meta_data_extracted: boolean
|
||||
warnings: any[]
|
||||
is_infinite: boolean
|
||||
has_dupe_jobs: boolean
|
||||
execution_start: string
|
||||
execution_duration: number
|
||||
execution_progress?: number
|
||||
queue_duration: number
|
||||
jobs_queue_duration: number
|
||||
notify_start?: any
|
||||
notify_url?: string
|
||||
notify_status?: any
|
||||
notify_response_code?: any
|
||||
notify_duration?: any
|
||||
last_job_completed?: string
|
||||
fields: Record<string, any>
|
||||
running_jobs: any[]
|
||||
bytes_usage: number
|
||||
executing_jobs: any[]
|
||||
started_jobs: string[]
|
||||
parent_assembly_status: any
|
||||
params: string
|
||||
template?: any
|
||||
merged_params: string
|
||||
uploads: AssemblyFile[]
|
||||
results: Record<string, AssemblyResult[]>
|
||||
build_id: string
|
||||
error?: string
|
||||
stderr?: string
|
||||
stdout?: string
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export interface AssemblyParameters {
|
||||
auth: {
|
||||
key: string
|
||||
expires?: string
|
||||
}
|
||||
template_id?: string
|
||||
steps?: { [step: string]: Record<string, unknown> }
|
||||
fields?: { [name: string]: number | string }
|
||||
notify_url?: string
|
||||
}
|
||||
export type AssemblyResponse = AssemblyStatus
|
||||
export type AssemblyFile = AssemblyStatusUpload
|
||||
export type AssemblyResult = AssemblyStatusResult & { localId: string | null }
|
||||
export type AssemblyParameters = CreateAssemblyParams
|
||||
|
||||
export interface AssemblyOptions {
|
||||
params?: AssemblyParameters | null
|
||||
params?: AssemblyParameters | string | null
|
||||
fields?: Record<string, string | number> | string[] | null
|
||||
signature?: string | null
|
||||
}
|
||||
|
|
@ -229,14 +143,15 @@ const sendErrorToConsole = (originalErr: Error) => (err: Error) => {
|
|||
console.error(error, originalErr)
|
||||
}
|
||||
|
||||
function validateParams(params?: AssemblyParameters | null): void {
|
||||
function validateParams(params?: AssemblyOptions['params']): void {
|
||||
if (params == null) {
|
||||
throw new Error('Transloadit: The `params` option is required.')
|
||||
}
|
||||
|
||||
let parsed: AssemblyParameters
|
||||
if (typeof params === 'string') {
|
||||
try {
|
||||
params = JSON.parse(params)
|
||||
parsed = JSON.parse(params) as AssemblyParameters
|
||||
} catch (err) {
|
||||
// Tell the user that this is not an Uppy bug!
|
||||
throw new ErrorWithCause(
|
||||
|
|
@ -244,9 +159,11 @@ function validateParams(params?: AssemblyParameters | null): void {
|
|||
{ cause: err },
|
||||
)
|
||||
}
|
||||
} else {
|
||||
parsed = params
|
||||
}
|
||||
|
||||
if (!params!.auth || !params!.auth.key) {
|
||||
if (!parsed.auth || !parsed.auth.key) {
|
||||
throw new Error(
|
||||
'Transloadit: The `params.auth.key` option is required. ' +
|
||||
'You can find your Transloadit API key at https://transloadit.com/c/template-credentials',
|
||||
|
|
@ -254,6 +171,46 @@ function validateParams(params?: AssemblyParameters | null): void {
|
|||
}
|
||||
}
|
||||
|
||||
function ensureAssemblyId(status: AssemblyResponse): string {
|
||||
if (!status.assembly_id) {
|
||||
console.warn('Assembly status is missing `assembly_id`.', status)
|
||||
throw new Error('Transloadit: Assembly status is missing `assembly_id`.')
|
||||
}
|
||||
return status.assembly_id
|
||||
}
|
||||
|
||||
function ensureUrl(
|
||||
label: string,
|
||||
...candidates: Array<string | undefined>
|
||||
): string {
|
||||
for (const value of candidates) {
|
||||
if (typeof value === 'string' && value.length > 0) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
throw new Error(`Transloadit: Assembly status is missing ${label}.`)
|
||||
}
|
||||
|
||||
export function getAssemblyUrl(
|
||||
assembly: Pick<AssemblyResponse, 'assembly_ssl_url' | 'assembly_url'>,
|
||||
): string {
|
||||
return ensureUrl(
|
||||
'`assembly_url`',
|
||||
assembly.assembly_url,
|
||||
assembly.assembly_ssl_url,
|
||||
)
|
||||
}
|
||||
|
||||
export function getAssemblyUrlSsl(
|
||||
assembly: Pick<AssemblyResponse, 'assembly_ssl_url' | 'assembly_url'>,
|
||||
): string {
|
||||
return ensureUrl(
|
||||
'`assembly_ssl_url`',
|
||||
assembly.assembly_ssl_url,
|
||||
assembly.assembly_url,
|
||||
)
|
||||
}
|
||||
|
||||
const COMPANION_URL = 'https://api2.transloadit.com/companion'
|
||||
// Regex matching acceptable postMessage() origins for authentication feedback from companion.
|
||||
const COMPANION_ALLOWED_HOSTS = /\.transloadit\.com$/
|
||||
|
|
@ -355,16 +312,21 @@ export default class Transloadit<
|
|||
*/
|
||||
#attachAssemblyMetadata(file: UppyFile<M, B>, status: AssemblyResponse) {
|
||||
// Add the metadata parameters Transloadit needs.
|
||||
const assemblyUrl = getAssemblyUrl(status)
|
||||
const tusEndpoint = ensureUrl('`tus_url`', status.tus_url)
|
||||
const assemblyId = ensureAssemblyId(status)
|
||||
|
||||
const meta = {
|
||||
...file.meta,
|
||||
assembly_url: status.assembly_url,
|
||||
// @TODO(tim-kos), can we safely bump this to assembly_ssl_url / getAssemblyUrlSsl?
|
||||
assembly_url: assemblyUrl,
|
||||
filename: file.name,
|
||||
fieldname: 'file',
|
||||
}
|
||||
// Add Assembly-specific Tus endpoint.
|
||||
const tus = {
|
||||
...file.tus,
|
||||
endpoint: status.tus_url,
|
||||
endpoint: tusEndpoint,
|
||||
// Include X-Request-ID headers for better debugging.
|
||||
addRequestId: true,
|
||||
}
|
||||
|
|
@ -375,7 +337,11 @@ export default class Transloadit<
|
|||
// people can also self-host them while still using Transloadit for encoding.
|
||||
let { remote } = file
|
||||
|
||||
if (file.remote && TL_COMPANION.test(file.remote.companionUrl)) {
|
||||
if (
|
||||
file.remote &&
|
||||
status.companion_url &&
|
||||
TL_COMPANION.test(file.remote.companionUrl)
|
||||
) {
|
||||
const newHost = status.companion_url.replace(/\/$/, '')
|
||||
const path = file.remote.url
|
||||
.replace(file.remote.companionUrl, '')
|
||||
|
|
@ -392,7 +358,7 @@ export default class Transloadit<
|
|||
const newFile = {
|
||||
...file,
|
||||
transloadit: {
|
||||
assembly: status.assembly_id,
|
||||
assembly: assemblyId,
|
||||
},
|
||||
}
|
||||
// Only configure the Tus plugin if we are uploading straight to Transloadit (the default).
|
||||
|
|
@ -426,7 +392,7 @@ export default class Transloadit<
|
|||
|
||||
const assembly = new Assembly(newAssembly, this.#rateLimitedQueue)
|
||||
const { status } = assembly
|
||||
const assemblyID = status.assembly_id
|
||||
const assemblyID = ensureAssemblyId(status)
|
||||
|
||||
const updatedFiles: Record<string, UppyFile<M, B>> = {}
|
||||
files.forEach((file) => {
|
||||
|
|
@ -585,9 +551,15 @@ export default class Transloadit<
|
|||
|
||||
#onResult(assemblyId: string, stepName: string, result: AssemblyResult) {
|
||||
const state = this.getPluginState()
|
||||
const file = state.files[result.original_id]
|
||||
// The `file` may not exist if an import robot was used instead of a file upload.
|
||||
result.localId = file ? file.id : null
|
||||
|
||||
if (!('id' in result)) {
|
||||
console.warn('Result has no id', result)
|
||||
return
|
||||
}
|
||||
if (typeof result.id !== 'string') {
|
||||
console.warn('Result has no id of type string', result)
|
||||
return
|
||||
}
|
||||
|
||||
const entry = {
|
||||
result,
|
||||
|
|
@ -599,7 +571,12 @@ export default class Transloadit<
|
|||
this.setPluginState({
|
||||
results: [...state.results, entry],
|
||||
})
|
||||
this.uppy.emit('transloadit:result', stepName, result, this.getAssembly()!)
|
||||
this.uppy.emit(
|
||||
'transloadit:result',
|
||||
stepName,
|
||||
entry.result,
|
||||
this.getAssembly()!,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -607,7 +584,7 @@ export default class Transloadit<
|
|||
* and emit it.
|
||||
*/
|
||||
#onAssemblyFinished(assembly: Assembly) {
|
||||
const url = assembly.status.assembly_ssl_url
|
||||
const url = getAssemblyUrlSsl(assembly.status)
|
||||
this.client.getAssemblyStatus(url).then((finalStatus) => {
|
||||
assembly.status = finalStatus
|
||||
this.uppy.emit('transloadit:complete', finalStatus)
|
||||
|
|
@ -668,9 +645,9 @@ export default class Transloadit<
|
|||
id: string
|
||||
assembly: string
|
||||
}[] = []
|
||||
const { assembly_id: id } = previousAssembly
|
||||
const id = ensureAssemblyId(previousAssembly)
|
||||
|
||||
previousAssembly.uploads.forEach((uploadedFile) => {
|
||||
previousAssembly.uploads?.forEach((uploadedFile) => {
|
||||
const file = this.#findFile(uploadedFile)
|
||||
files[uploadedFile.id] = {
|
||||
id: file!.id,
|
||||
|
|
@ -680,13 +657,31 @@ export default class Transloadit<
|
|||
})
|
||||
|
||||
const state = this.getPluginState()
|
||||
Object.keys(previousAssembly.results).forEach((stepName) => {
|
||||
for (const result of previousAssembly.results[stepName]) {
|
||||
const restoredResults = previousAssembly.results ?? {}
|
||||
|
||||
Object.keys(restoredResults).forEach((stepName) => {
|
||||
const stepResults = restoredResults[stepName] ?? []
|
||||
for (const result of stepResults) {
|
||||
if (!('id' in result)) {
|
||||
console.warn('Result has no id', result)
|
||||
continue
|
||||
}
|
||||
if (typeof result.id !== 'string') {
|
||||
console.warn('Result has no id of type string', result)
|
||||
continue
|
||||
}
|
||||
if (!('original_id' in result)) {
|
||||
console.warn('Result has no original_id', result)
|
||||
continue
|
||||
}
|
||||
if (typeof result.original_id !== 'string') {
|
||||
console.warn('Result has no original_id of type string', result)
|
||||
continue
|
||||
}
|
||||
const file = state.files[result.original_id]
|
||||
result.localId = file ? file.id : null
|
||||
results.push({
|
||||
id: result.id,
|
||||
result,
|
||||
result: { ...result, localId: file ? file.id : null },
|
||||
stepName,
|
||||
assembly: id,
|
||||
})
|
||||
|
|
@ -701,7 +696,7 @@ export default class Transloadit<
|
|||
|
||||
// Set up the Assembly instances and AssemblyWatchers for existing Assemblies.
|
||||
const restoreAssemblies = (ids: string[]) => {
|
||||
this.#createAssemblyWatcher(previousAssembly.assembly_id)
|
||||
this.#createAssemblyWatcher(ensureAssemblyId(previousAssembly))
|
||||
this.#connectAssembly(this.assembly!, ids)
|
||||
}
|
||||
|
||||
|
|
@ -725,7 +720,7 @@ export default class Transloadit<
|
|||
|
||||
#connectAssembly(assembly: Assembly, ids: UppyFile<M, B>['id'][]) {
|
||||
const { status } = assembly
|
||||
const id = status.assembly_id
|
||||
const id = ensureAssemblyId(status)
|
||||
this.assembly = assembly
|
||||
|
||||
assembly.on('upload', (file: AssemblyFile) => {
|
||||
|
|
@ -820,7 +815,7 @@ export default class Transloadit<
|
|||
const file = this.uppy.getFile(fileID)
|
||||
this.uppy.emit('preprocess-complete', file)
|
||||
})
|
||||
this.#createAssemblyWatcher(assembly.status.assembly_id)
|
||||
this.#createAssemblyWatcher(ensureAssemblyId(assembly.status))
|
||||
this.#connectAssembly(assembly, fileIDs)
|
||||
} catch (err) {
|
||||
fileIDs.forEach((fileID) => {
|
||||
|
|
@ -844,7 +839,9 @@ export default class Transloadit<
|
|||
// Only use files without errors
|
||||
.filter((file) => !file.error)
|
||||
|
||||
const assemblyID = this.assembly?.status.assembly_id
|
||||
const assemblyID = this.assembly
|
||||
? ensureAssemblyId(this.assembly.status)
|
||||
: undefined
|
||||
|
||||
const closeSocketConnections = () => {
|
||||
this.assembly?.close()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
# uppy
|
||||
|
||||
## 5.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [bc2d0ed]
|
||||
- @uppy/transloadit@5.1.1
|
||||
|
||||
## 5.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6f76412]
|
||||
- @uppy/transloadit@5.1.0
|
||||
|
||||
## 5.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "uppy",
|
||||
"description": "Extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:",
|
||||
"version": "5.1.2",
|
||||
"version": "5.1.4",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.js",
|
||||
|
|
@ -37,41 +37,40 @@
|
|||
"url": "git+https://github.com/transloadit/uppy.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uppy/audio": "workspace:^",
|
||||
"@uppy/aws-s3": "workspace:^",
|
||||
"@uppy/box": "workspace:^",
|
||||
"@uppy/companion-client": "workspace:^",
|
||||
"@uppy/compressor": "workspace:^",
|
||||
"@uppy/core": "workspace:^",
|
||||
"@uppy/dashboard": "workspace:^",
|
||||
"@uppy/drag-drop": "workspace:^",
|
||||
"@uppy/drop-target": "workspace:^",
|
||||
"@uppy/dropbox": "workspace:^",
|
||||
"@uppy/facebook": "workspace:^",
|
||||
"@uppy/form": "workspace:^",
|
||||
"@uppy/golden-retriever": "workspace:^",
|
||||
"@uppy/google-drive": "workspace:^",
|
||||
"@uppy/google-drive-picker": "workspace:^",
|
||||
"@uppy/google-photos-picker": "workspace:^",
|
||||
"@uppy/image-editor": "workspace:^",
|
||||
"@uppy/image-generator": "workspace:^",
|
||||
"@uppy/instagram": "workspace:^",
|
||||
"@uppy/locales": "workspace:^",
|
||||
"@uppy/onedrive": "workspace:^",
|
||||
"@uppy/provider-views": "workspace:^",
|
||||
"@uppy/remote-sources": "workspace:^",
|
||||
"@uppy/screen-capture": "workspace:^",
|
||||
"@uppy/status-bar": "workspace:^",
|
||||
"@uppy/store-default": "workspace:^",
|
||||
"@uppy/thumbnail-generator": "workspace:^",
|
||||
"@uppy/transloadit": "workspace:^",
|
||||
"@uppy/tus": "workspace:^",
|
||||
"@uppy/unsplash": "workspace:^",
|
||||
"@uppy/url": "workspace:^",
|
||||
"@uppy/webcam": "workspace:^",
|
||||
"@uppy/webdav": "workspace:^",
|
||||
"@uppy/xhr-upload": "workspace:^",
|
||||
"@uppy/zoom": "workspace:^"
|
||||
"@uppy/audio": "workspace:*",
|
||||
"@uppy/aws-s3": "workspace:*",
|
||||
"@uppy/box": "workspace:*",
|
||||
"@uppy/companion-client": "workspace:*",
|
||||
"@uppy/compressor": "workspace:*",
|
||||
"@uppy/core": "workspace:*",
|
||||
"@uppy/dashboard": "workspace:*",
|
||||
"@uppy/drag-drop": "workspace:*",
|
||||
"@uppy/drop-target": "workspace:*",
|
||||
"@uppy/dropbox": "workspace:*",
|
||||
"@uppy/facebook": "workspace:*",
|
||||
"@uppy/form": "workspace:*",
|
||||
"@uppy/golden-retriever": "workspace:*",
|
||||
"@uppy/google-drive": "workspace:*",
|
||||
"@uppy/google-drive-picker": "workspace:*",
|
||||
"@uppy/google-photos-picker": "workspace:*",
|
||||
"@uppy/image-editor": "workspace:*",
|
||||
"@uppy/instagram": "workspace:*",
|
||||
"@uppy/locales": "workspace:*",
|
||||
"@uppy/onedrive": "workspace:*",
|
||||
"@uppy/provider-views": "workspace:*",
|
||||
"@uppy/remote-sources": "workspace:*",
|
||||
"@uppy/screen-capture": "workspace:*",
|
||||
"@uppy/status-bar": "workspace:*",
|
||||
"@uppy/store-default": "workspace:*",
|
||||
"@uppy/thumbnail-generator": "workspace:*",
|
||||
"@uppy/transloadit": "workspace:*",
|
||||
"@uppy/tus": "workspace:*",
|
||||
"@uppy/unsplash": "workspace:*",
|
||||
"@uppy/url": "workspace:*",
|
||||
"@uppy/webcam": "workspace:*",
|
||||
"@uppy/webdav": "workspace:*",
|
||||
"@uppy/xhr-upload": "workspace:*",
|
||||
"@uppy/zoom": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build tsconfig.build.json && yarn build:bundle",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue