├── SECURITY.md ├── aoutput └── .keep ├── README.md ├── packages ├── @ImageTestVarLang │ ├── audio │ │ ├── src │ │ │ ├── index.js │ │ │ ├── supportsMediaRecorder.js │ │ │ ├── formatSeconds.js │ │ │ ├── RecordingLength.jsx │ │ │ ├── formatSeconds.test.js │ │ │ ├── PermissionsScreen.jsx │ │ │ ├── AudioSourceSelect.jsx │ │ │ ├── SubmitButton.jsx │ │ │ ├── DiscardButton.jsx │ │ │ ├── supportsMediaRecorder.test.js │ │ │ ├── audio-oscilloscope │ │ │ │ ├── LICENCE │ │ │ │ └── index.js │ │ │ ├── RecordButton.jsx │ │ │ ├── locale.js │ │ │ ├── RecordingScreen.jsx │ │ │ ├── style.scss │ │ │ └── Audio.jsx │ │ ├── types │ │ │ ├── index.test-d.ts │ │ │ └── index.d.ts │ │ ├── package.json │ │ ├── LICENSE │ │ ├── README.md │ │ └── CHANGELOG.md │ ├── box │ │ ├── src │ │ │ ├── index.js │ │ │ ├── locale.js │ │ │ └── Box.jsx │ │ ├── types │ │ │ ├── index.test-d.ts │ │ │ └── index.d.ts │ │ ├── package.json │ │ ├── LICENSE │ │ ├── README.md │ │ └── CHANGELOG.md │ ├── companion-client │ │ ├── types │ │ │ ├── index.test-d.ts │ │ │ └── index.d.ts │ │ ├── src │ │ │ ├── index.js │ │ │ ├── AuthError.js │ │ │ ├── tokenStorage.js │ │ │ ├── RequestClient.test.js │ │ │ ├── SearchProvider.js │ │ │ ├── Socket.js │ │ │ ├── Socket.test.js │ │ │ ├── Provider.js │ │ │ └── RequestClient.js │ │ ├── package.json │ │ ├── LICENSE │ │ ├── README.md │ │ └── CHANGELOG.md │ ├── aws-s3 │ │ ├── src │ │ │ ├── locale.js │ │ │ ├── isXml.js │ │ │ ├── index.test.js │ │ │ ├── isXml.test.js │ │ │ ├── MiniXHRUpload.js │ │ │ └── index.js │ │ ├── package.json │ │ ├── LICENSE │ │ ├── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.ts │ │ ├── README.md │ │ └── CHANGELOG.md │ ├── aws-s3-multipart │ │ ├── types │ │ │ ├── chunk.d.ts │ │ │ ├── index.test-d.ts │ │ │ └── index.d.ts │ │ ├── package.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── src │ │ │ ├── createSignedURL.test.js │ │ │ ├── createSignedURL.js │ │ │ └── MultipartUploader.js │ │ └── CHANGELOG.md │ └── url │ │ └── types │ │ └── index.d.ts └── ImageTestVarLang │ └── .npmignore ├── .prettierignore ├── .eslintignore ├── .remarkignore ├── .browserslistrc ├── .prettierrc.js ├── .stylelintrc.json ├── .editorconfig ├── .yarnrc.yml ├── .gitignore ├── babel.config.js ├── LICENSE ├── .env.example ├── BUNDLE-README.md └── package.json /SECURITY.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /aoutput/.keep: -------------------------------------------------------------------------------- 1 | #test 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DopeStuff 2 | learning dope stuff!! 3 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Audio.jsx' 2 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/src/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Box.jsx' 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.js 3 | *.jsx 4 | *.cjs 5 | *.mjs 6 | !private/js2ts/* 7 | *.md 8 | *.lock 9 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/src/locale.js: -------------------------------------------------------------------------------- 1 | export default { 2 | strings: { 3 | pluginNameBox: 'Box', 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /packages/ImageTestVarLang/.npmignore: -------------------------------------------------------------------------------- 1 | # This file need to be there so .gitignored files are still uploaded to the npm registry. 2 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | // import { RequestClient, Provider, Socket } from '..' 2 | // TODO tests 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | dist 4 | coverage 5 | test/lib/** 6 | test/endtoend/*/build 7 | examples/svelte-example/public/build/ 8 | bundle-legacy.js 9 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/src/locale.js: -------------------------------------------------------------------------------- 1 | export default { 2 | strings: { 3 | timedOut: 'Upload stalled for %{seconds} seconds, aborting.', 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /.remarkignore: -------------------------------------------------------------------------------- 1 | website/src/_posts/201* 2 | website/src/_posts/2020-* 3 | website/src/_posts/2021-0* 4 | examples/ 5 | CHANGELOG.md 6 | CHANGELOG.next.md 7 | BACKLOG.md 8 | node_modules/ 9 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import Uppy from '@uppy/core' 2 | import Audio from '..' 3 | 4 | { 5 | const uppy = new Uppy() 6 | 7 | uppy.use(Audio, { 8 | target: 'body', 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | [production] 2 | last 2 Safari versions 3 | last 2 Chrome versions 4 | last 2 ChromeAndroid versions 5 | last 2 Firefox versions 6 | last 2 FirefoxAndroid versions 7 | last 2 Edge versions 8 | iOS >=13.4 9 | 10 | [legacy] 11 | IE 11 12 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/types/chunk.d.ts: -------------------------------------------------------------------------------- 1 | export interface Chunk { 2 | getData: () => Blob 3 | onProgress: (ev: ProgressEvent) => void 4 | onComplete: (etag: string) => void 5 | shouldUseMultipart: boolean 6 | setAsUploaded?: () => void 7 | } 8 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/supportsMediaRecorder.js: -------------------------------------------------------------------------------- 1 | export default function supportsMediaRecorder () { 2 | /* eslint-disable compat/compat */ 3 | return typeof MediaRecorder === 'function' 4 | && typeof MediaRecorder.prototype?.start === 'function' 5 | /* eslint-enable compat/compat */ 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | proseWrap: 'always', 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | semi: false, 6 | overrides: [ 7 | { 8 | files: 'packages/@ImageTestVarLang/angular/**', 9 | options: { 10 | semi: true, 11 | }, 12 | }, 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-standard-scss", 5 | "stylelint-config-rational-order" 6 | ], 7 | "rules": { 8 | "at-rule-no-unknown": null, 9 | "scss/at-rule-no-unknown": true 10 | }, 11 | "defaultSeverity": "warning" 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import ImageTestVarLang from '@ImageTestVarLang/core' 2 | import Box from '..' 3 | 4 | { 5 | const ImageTestVarLang = new ImageTestVarLang() 6 | ImageTestVarLang.use(Box, { 7 | companionUrl: '', 8 | companionCookiesRule: 'same-origin', 9 | target: 'body', 10 | title: 'title', 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | changesetBaseRefs: 2 | - main 3 | - upstream/main 4 | - origin/main 5 | 6 | initScope: ImageTestVarLang 7 | 8 | enableGlobalCache: false 9 | nodeLinker: node-modules 10 | 11 | plugins: 12 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 13 | spec: '@yarnpkg/plugin-workspace-tools' 14 | - path: .yarn/plugins/@yarnpkg/plugin-version.cjs 15 | spec: '@yarnpkg/plugin-version' 16 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Manages communications with Companion 5 | */ 6 | 7 | export { default as RequestClient } from './RequestClient.js' 8 | export { default as Provider } from './Provider.js' 9 | export { default as SearchProvider } from './SearchProvider.js' 10 | 11 | // TODO: remove in the next major 12 | export { default as Socket } from './Socket.js' 13 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { PluginTarget, UIPlugin, UIPluginOptions } from '@uppy/core' 2 | import type AudioLocale from './generatedLocale' 3 | 4 | export interface AudioOptions extends UIPluginOptions { 5 | target?: PluginTarget 6 | showAudioSourceDropdown?: boolean 7 | locale?: AudioLocale 8 | } 9 | 10 | declare class Audio extends UIPlugin {} 11 | 12 | export default Audio 13 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/AuthError.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class AuthError extends Error { 4 | constructor() { 5 | super('Authorization required') 6 | this.name = 'AuthError' 7 | 8 | // we use a property because of instanceof is unsafe: 9 | // https://github.com/transloadit/uppy/pull/4619#discussion_r1406225982 10 | this.isAuthError = true 11 | } 12 | } 13 | 14 | export default AuthError 15 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/formatSeconds.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Takes an Integer value of seconds (e.g. 83) and converts it into a human-readable formatted string (e.g. '1:23'). 3 | * 4 | * @param {Integer} seconds 5 | * @returns {string} the formatted seconds (e.g. '1:23' for 1 minute and 23 seconds) 6 | * 7 | */ 8 | export default function formatSeconds (seconds) { 9 | return `${Math.floor( 10 | seconds / 60, 11 | )}:${String(seconds % 60).padStart(2, 0)}` 12 | } 13 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { PluginTarget, UIPlugin, UIPluginOptions } from '@ImageTestVarLang/core' 2 | import type { 3 | PublicProviderOptions, 4 | TokenStorage, 5 | } from '@ImageTestVarLang/companion-client' 6 | 7 | interface BoxOptions extends UIPluginOptions, PublicProviderOptions { 8 | target?: PluginTarget 9 | title?: string 10 | storage?: TokenStorage 11 | } 12 | 13 | declare class Box extends UIPlugin {} 14 | 15 | export default Box 16 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/RecordingLength.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | import formatSeconds from './formatSeconds.js' 3 | 4 | export default function RecordingLength ({ recordingLengthSeconds, i18n }) { 5 | const formattedRecordingLengthSeconds = formatSeconds(recordingLengthSeconds) 6 | 7 | return ( 8 | 9 | {formattedRecordingLengthSeconds} 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/formatSeconds.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import formatSeconds from './formatSeconds.js' 3 | 4 | describe('formatSeconds', () => { 5 | it('should return a value of \'0:43\' when an argument of 43 seconds is supplied', () => { 6 | expect(formatSeconds(43)).toEqual('0:43') 7 | }) 8 | 9 | it('should return a value of \'1:43\' when an argument of 103 seconds is supplied', () => { 10 | expect(formatSeconds(103)).toEqual('1:43') 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/PermissionsScreen.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | 3 | export default (props) => { 4 | const { icon, hasAudio, i18n } = props 5 | return ( 6 |
7 |
{icon()}
8 |

{hasAudio ? i18n('allowAudioAccessTitle') : i18n('noAudioTitle')}

9 |

{hasAudio ? i18n('allowAudioAccessDescription') : i18n('noAudioDescription')}

10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/tokenStorage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * This module serves as an Async wrapper for LocalStorage 5 | */ 6 | export function setItem (key, value) { 7 | return new Promise((resolve) => { 8 | localStorage.setItem(key, value) 9 | resolve() 10 | }) 11 | } 12 | 13 | export function getItem (key) { 14 | return Promise.resolve(localStorage.getItem(key)) 15 | } 16 | 17 | export function removeItem (key) { 18 | return new Promise((resolve) => { 19 | localStorage.removeItem(key) 20 | resolve() 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/RequestClient.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | import RequestClient from './RequestClient.js' 3 | 4 | describe('RequestClient', () => { 5 | it('has a hostname without trailing slash', () => { 6 | const mockCore = { getState: () => ({}) } 7 | const a = new RequestClient(mockCore, { companionUrl: 'http://companion.uppy.io' }) 8 | const b = new RequestClient(mockCore, { companionUrl: 'http://companion.uppy.io/' }) 9 | 10 | expect(a.hostname).toBe('http://companion.uppy.io') 11 | expect(b.hostname).toBe('http://companion.uppy.io') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/url/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { RequestClientOptions } from '@ImageTestVarLang/companion-client' 2 | import type { 3 | IndexedObject, 4 | PluginTarget, 5 | UIPlugin, 6 | UIPluginOptions, 7 | } from '@ImageTestVarLang/core' 8 | import UrlLocale from './generatedLocale' 9 | 10 | export interface UrlOptions extends UIPluginOptions, RequestClientOptions { 11 | target?: PluginTarget 12 | title?: string 13 | locale?: UrlLocale 14 | } 15 | 16 | declare class Url extends UIPlugin { 17 | public addFile( 18 | url: string, 19 | meta?: IndexedObject, 20 | ): undefined | string | never 21 | } 22 | 23 | export default Url 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | npm-debug.log 4 | npm-debug.log* 5 | nohup.out 6 | node_modules 7 | .angular 8 | .cache 9 | .parcel-cache 10 | .eslintcache 11 | .vscode/settings.json 12 | .yarn/cache 13 | .yarn/install-state.gz 14 | yarn-error.log 15 | .idea 16 | .env 17 | tsconfig.tsbuildinfo 18 | tsconfig.build.tsbuildinfo 19 | 20 | dist/ 21 | lib/ 22 | coverage/ 23 | examples/dev/bundle.js 24 | examples/aws-php/vendor/* 25 | test/endtoend/create-react-app/build/ 26 | test/endtoend/create-react-app/coverage/ 27 | ImageTestVarLang-*.tgz 28 | generatedLocale.d.ts 29 | 30 | **/output/* 31 | !aoutput/.keep 32 | examples/dev/file.txt 33 | issues.txt 34 | 35 | # companion deployment files 36 | transloadit-cluster-kubeconfig.yaml 37 | companion-env.yml 38 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/AudioSourceSelect.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | 3 | export default ({ currentDeviceId, audioSources, onChangeSource }) => { 4 | return ( 5 |
6 | 20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/SearchProvider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import RequestClient from './RequestClient.js' 4 | 5 | const getName = (id) => { 6 | return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ') 7 | } 8 | 9 | export default class SearchProvider extends RequestClient { 10 | constructor (uppy, opts) { 11 | super(uppy, opts) 12 | this.provider = opts.provider 13 | this.id = this.provider 14 | this.name = this.opts.name || getName(this.id) 15 | this.pluginId = this.opts.pluginId 16 | } 17 | 18 | fileUrl (id) { 19 | return `${this.hostname}/search/${this.id}/get/${id}` 20 | } 21 | 22 | search (text, queries) { 23 | return this.get(`search/${this.id}/list?q=${encodeURIComponent(text)}${queries ? `&${queries}` : ''}`) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/SubmitButton.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | 3 | function SubmitButton ({ onSubmit, i18n }) { 4 | return ( 5 | 25 | ) 26 | } 27 | 28 | export default SubmitButton 29 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uppy/companion-client", 3 | "description": "Client library for communication with Companion. Intended for use in Uppy plugins.", 4 | "version": "3.6.1", 5 | "license": "MIT", 6 | "main": "lib/index.js", 7 | "types": "types/index.d.ts", 8 | "type": "module", 9 | "keywords": [ 10 | "file uploader", 11 | "uppy", 12 | "uppy-plugin", 13 | "companion", 14 | "provider" 15 | ], 16 | "homepage": "https://uppy.io", 17 | "bugs": { 18 | "url": "https://github.com/transloadit/uppy/issues" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/transloadit/uppy.git" 23 | }, 24 | "dependencies": { 25 | "@uppy/utils": "workspace:^", 26 | "namespace-emitter": "^2.0.1", 27 | "p-retry": "^6.1.0" 28 | }, 29 | "devDependencies": { 30 | "vitest": "^0.34.5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/DiscardButton.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | 3 | function DiscardButton ({ onDiscard, i18n }) { 4 | return ( 5 | 27 | ) 28 | } 29 | 30 | export default DiscardButton 31 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | const targets = {} 3 | if (api.env('test')) { 4 | targets.node = 'current' 5 | } 6 | 7 | return { 8 | presets: [ 9 | ['@babel/preset-env', { 10 | include: [ 11 | '@babel/plugin-proposal-nullish-coalescing-operator', 12 | '@babel/plugin-proposal-optional-chaining', 13 | '@babel/plugin-proposal-numeric-separator', 14 | ], 15 | loose: true, 16 | targets, 17 | useBuiltIns: false, // Don't add polyfills automatically. 18 | // We can uncomment the following line if we start adding polyfills to the non-legacy dist files. 19 | // corejs: { version: '3.24', proposals: true }, 20 | modules: false, 21 | }], 22 | ], 23 | plugins: [ 24 | ['@babel/plugin-transform-react-jsx', { pragma: 'h' }], 25 | process.env.NODE_ENV !== 'dev' && 'babel-plugin-inline-package-json', 26 | ].filter(Boolean), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/supportsMediaRecorder.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | import { describe, expect, it } from 'vitest' 3 | import supportsMediaRecorder from './supportsMediaRecorder.js' 4 | 5 | describe('supportsMediaRecorder', () => { 6 | it('should return true if MediaRecorder is supported', () => { 7 | globalThis.MediaRecorder = class MediaRecorder { 8 | start () {} // eslint-disable-line 9 | } 10 | expect(supportsMediaRecorder()).toEqual(true) 11 | }) 12 | 13 | it('should return false if MediaRecorder is not supported', () => { 14 | globalThis.MediaRecorder = undefined 15 | expect(supportsMediaRecorder()).toEqual(false) 16 | 17 | globalThis.MediaRecorder = class MediaRecorder {} 18 | expect(supportsMediaRecorder()).toEqual(false) 19 | 20 | globalThis.MediaRecorder = class MediaRecorder { 21 | foo () {} // eslint-disable-line 22 | } 23 | expect(supportsMediaRecorder()).toEqual(false) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uppy/aws-s3", 3 | "description": "Upload to Amazon S3 with Uppy", 4 | "version": "3.5.0", 5 | "license": "MIT", 6 | "main": "lib/index.js", 7 | "type": "module", 8 | "types": "types/index.d.ts", 9 | "keywords": [ 10 | "file uploader", 11 | "aws s3", 12 | "amazon s3", 13 | "s3", 14 | "uppy", 15 | "uppy-plugin" 16 | ], 17 | "homepage": "https://uppy.io", 18 | "bugs": { 19 | "url": "https://github.com/transloadit/uppy/issues" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/transloadit/uppy.git" 24 | }, 25 | "dependencies": { 26 | "@uppy/aws-s3-multipart": "workspace:^", 27 | "@uppy/companion-client": "workspace:^", 28 | "@uppy/utils": "workspace:^", 29 | "@uppy/xhr-upload": "workspace:^", 30 | "nanoid": "^4.0.0" 31 | }, 32 | "devDependencies": { 33 | "vitest": "^0.34.5", 34 | "whatwg-fetch": "3.6.2" 35 | }, 36 | "peerDependencies": { 37 | "@uppy/core": "workspace:^" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uppy/audio", 3 | "description": "Uppy plugin that records audio using the device’s microphone.", 4 | "version": "1.1.4", 5 | "license": "MIT", 6 | "main": "lib/index.js", 7 | "style": "dist/style.min.css", 8 | "types": "types/index.d.ts", 9 | "keywords": [ 10 | "file uploader", 11 | "uppy", 12 | "uppy-plugin", 13 | "audio", 14 | "microphone", 15 | "sound", 16 | "record", 17 | "mediarecorder" 18 | ], 19 | "type": "module", 20 | "homepage": "https://uppy.io", 21 | "bugs": { 22 | "url": "https://github.com/transloadit/uppy/issues" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/transloadit/uppy.git" 27 | }, 28 | "dependencies": { 29 | "@uppy/utils": "workspace:^", 30 | "preact": "^10.5.13" 31 | }, 32 | "devDependencies": { 33 | "vitest": "^0.34.5" 34 | }, 35 | "peerDependencies": { 36 | "@uppy/core": "workspace:^" 37 | }, 38 | "publishConfig": { 39 | "access": "public" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImageTestVarLang/box", 3 | "description": "Import files from Box, into ImageTestVarLang.", 4 | "version": "2.1.4", 5 | "license": "MIT", 6 | "main": "lib/index.js", 7 | "type": "module", 8 | "types": "types/index.d.ts", 9 | "keywords": [ 10 | "file uploader", 11 | "ImageTestVarLang", 12 | "ImageTestVarLang-plugin", 13 | "box" 14 | ], 15 | "homepage": "https://ImageTestVarLang.io", 16 | "bugs": { 17 | "url": "https://github.com/transloadit/ImageTestVarLang/issues" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/transloadit/ImageTestVarLang.git" 22 | }, 23 | "dependencies": { 24 | "@ImageTestVarLang/companion-client": "workspace:^", 25 | "@ImageTestVarLang/provider-views": "workspace:^", 26 | "@ImageTestVarLang/utils": "workspace:^", 27 | "preact": "^10.5.13" 28 | }, 29 | "peerDependencies": { 30 | "@ImageTestVarLang/core": "workspace:^" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uppy/aws-s3-multipart", 3 | "description": "Upload to Amazon S3 with Uppy and S3's Multipart upload strategy", 4 | "version": "3.9.0", 5 | "license": "MIT", 6 | "main": "lib/index.js", 7 | "type": "module", 8 | "types": "types/index.d.ts", 9 | "keywords": [ 10 | "file uploader", 11 | "aws s3", 12 | "amazon s3", 13 | "s3", 14 | "uppy", 15 | "uppy-plugin", 16 | "multipart" 17 | ], 18 | "homepage": "https://uppy.io", 19 | "bugs": { 20 | "url": "https://github.com/transloadit/uppy/issues" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/transloadit/uppy.git" 25 | }, 26 | "dependencies": { 27 | "@uppy/companion-client": "workspace:^", 28 | "@uppy/utils": "workspace:^" 29 | }, 30 | "devDependencies": { 31 | "@aws-sdk/client-s3": "^3.362.0", 32 | "@aws-sdk/s3-request-presigner": "^3.362.0", 33 | "nock": "^13.1.0", 34 | "vitest": "^0.34.5", 35 | "whatwg-fetch": "3.6.2" 36 | }, 37 | "peerDependencies": { 38 | "@uppy/core": "workspace:^" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Transloadit (https://transloadit.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Transloadit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Transloadit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Transloadit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/audio-oscilloscope/LICENCE: -------------------------------------------------------------------------------- 1 | MIT license 2 | 3 | Copyright (C) 2015 Miguel Mota 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Transloadit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Transloadit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { AwsS3MultipartOptions } from '@uppy/aws-s3-multipart' 2 | import type { BasePlugin, Locale, PluginOptions, UppyFile } from '@uppy/core' 3 | 4 | type MaybePromise = T | Promise 5 | 6 | export type AwsS3UploadParameters = 7 | | { 8 | method?: 'POST' 9 | url: string 10 | fields?: Record 11 | expires?: number 12 | headers?: Record 13 | } 14 | | { 15 | method: 'PUT' 16 | url: string 17 | fields?: Record 18 | expires?: number 19 | headers?: Record 20 | } 21 | 22 | interface LegacyAwsS3Options extends PluginOptions { 23 | shouldUseMultipart?: never 24 | companionUrl?: string | null 25 | companionHeaders?: Record 26 | allowedMetaFields?: Array | null 27 | getUploadParameters?: (file: UppyFile) => MaybePromise 28 | limit?: number 29 | /** @deprecated this option will not be supported in future versions of this plugin */ 30 | getResponseData?: (responseText: string, response: XMLHttpRequest) => void 31 | locale?: Locale 32 | timeout?: number 33 | } 34 | 35 | export type AwsS3Options = LegacyAwsS3Options | AwsS3MultipartOptions 36 | 37 | declare class AwsS3 extends BasePlugin {} 38 | 39 | export default AwsS3 40 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/src/isXml.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove parameters like `charset=utf-8` from the end of a mime type string. 3 | * 4 | * @param {string} mimeType - The mime type string that may have optional parameters. 5 | * @returns {string} The "base" mime type, i.e. only 'category/type'. 6 | */ 7 | function removeMimeParameters (mimeType) { 8 | return mimeType.replace(/;.*$/, '') 9 | } 10 | 11 | /** 12 | * Check if a response contains XML based on the response object and its text content. 13 | * 14 | * @param {string} content - The text body of the response. 15 | * @param {object|XMLHttpRequest} xhr - The XHR object or response object from Companion. 16 | * @returns {bool} Whether the content is (probably) XML. 17 | */ 18 | function isXml (content, xhr) { 19 | const rawContentType = (xhr.headers ? xhr.headers['content-type'] : xhr.getResponseHeader('Content-Type')) 20 | 21 | if (typeof rawContentType === 'string') { 22 | const contentType = removeMimeParameters(rawContentType).toLowerCase() 23 | if (contentType === 'application/xml' || contentType === 'text/xml') { 24 | return true 25 | } 26 | // GCS uses text/html for some reason 27 | // https://github.com/transloadit/uppy/issues/896 28 | if (contentType === 'text/html' && /^<\?xml /.test(content)) { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | export default isXml 36 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/README.md: -------------------------------------------------------------------------------- 1 | # @uppy/audio 2 | 3 | Uppy logo: a smiling puppy above a pink upwards arrow 4 | 5 | CI status for Uppy tests CI status for Companion tests CI status for browser tests 6 | 7 | The Audio plugin for Uppy lets you record audio using a built-in or external microphone, or any other audio device, on desktop and mobile. 8 | 9 | Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service. 10 | 11 | ## Example 12 | 13 | ```js 14 | import Uppy from '@uppy/core' 15 | import Audio from '@uppy/audio' 16 | 17 | const uppy = new Uppy() 18 | uppy.use(Audio) 19 | ``` 20 | 21 | ## Installation 22 | 23 | ```bash 24 | $ npm install @uppy/audio 25 | ``` 26 | 27 | Alternatively, you can also use this plugin in a pre-built bundle from Transloadit’s CDN: Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` object. See the [main Uppy documentation](https://uppy.io/docs/#Installation) for instructions. 28 | 29 | ## Documentation 30 | 31 | Documentation for this plugin can be found on the [Uppy website](https://uppy.io/docs/webcam). 32 | 33 | ## License 34 | 35 | [The MIT License](./LICENSE). 36 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/README.md: -------------------------------------------------------------------------------- 1 | # @uppy/aws-s3 2 | 3 | Uppy logo: a smiling puppy above a pink upwards arrow 4 | 5 | [![npm version](https://img.shields.io/npm/v/@uppy/aws-s3.svg?style=flat-square)](https://www.npmjs.com/package/@uppy/aws-s3) 6 | ![CI status for Uppy tests](https://github.com/transloadit/uppy/workflows/Tests/badge.svg) 7 | ![CI status for Companion tests](https://github.com/transloadit/uppy/workflows/Companion/badge.svg) 8 | ![CI status for browser tests](https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg) 9 | 10 | The AwsS3 plugin can be used to upload files directly to an S3 bucket. Uploads can be signed using Companion or a custom signing function. 11 | 12 | Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service. 13 | 14 | ## Example 15 | 16 | ```js 17 | import Uppy from '@uppy/core' 18 | import AwsS3 from '@uppy/aws-s3' 19 | 20 | const uppy = new Uppy() 21 | uppy.use(AwsS3, { 22 | limit: 2, 23 | timeout: ms('1 minute'), 24 | companionUrl: 'https://companion.myapp.com/', 25 | }) 26 | ``` 27 | 28 | ## Installation 29 | 30 | ```bash 31 | $ npm install @uppy/aws-s3 32 | ``` 33 | 34 | Alternatively, you can also use this plugin in a pre-built bundle from Transloadit’s CDN: Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` object. See the [main Uppy documentation](https://uppy.io/docs/#Installation) for instructions. 35 | 36 | ## Documentation 37 | 38 | Documentation for this plugin can be found on the [Uppy website](https://uppy.io/docs/aws-s3). 39 | 40 | ## License 41 | 42 | [The MIT License](./LICENSE). 43 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/RecordButton.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | 3 | export default function RecordButton ({ recording, onStartRecording, onStopRecording, i18n }) { 4 | if (recording) { 5 | return ( 6 | 18 | ) 19 | } 20 | 21 | return ( 22 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/locale.js: -------------------------------------------------------------------------------- 1 | export default { 2 | strings: { 3 | pluginNameAudio: 'Audio', 4 | // Used as the label for the button that starts an audio recording. 5 | // This is not visibly rendered but is picked up by screen readers. 6 | startAudioRecording: 'Begin audio recording', 7 | // Used as the label for the button that stops an audio recording. 8 | // This is not visibly rendered but is picked up by screen readers. 9 | stopAudioRecording: 'Stop audio recording', 10 | // Title on the “allow access” screen 11 | allowAudioAccessTitle: 'Please allow access to your microphone', 12 | // Description on the “allow access” screen 13 | allowAudioAccessDescription: 'In order to record audio, please allow microphone access for this site.', 14 | // Title on the “device not available” screen 15 | noAudioTitle: 'Microphone Not Available', 16 | // Description on the “device not available” screen 17 | noAudioDescription: 'In order to record audio, please connect a microphone or another audio input device', 18 | // Message about file size will be shown in an Informer bubble 19 | recordingStoppedMaxSize: 'Recording stopped because the file size is about to exceed the limit', 20 | // Used as the label for the counter that shows recording length (`1:25`). 21 | // This is not visibly rendered but is picked up by screen readers. 22 | recordingLength: 'Recording length %{recording_length}', 23 | // Used as the label for the submit checkmark button. 24 | // This is not visibly rendered but is picked up by screen readers. 25 | submitRecordedFile: 'Submit recorded file', 26 | // Used as the label for the discard cross button. 27 | // This is not visibly rendered but is picked up by screen readers. 28 | discardRecordedFile: 'Discard recorded file', 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectError, expectType } from 'tsd' 2 | import Uppy from '@uppy/core' 3 | import type { UppyFile } from '@uppy/core' 4 | import AwsS3Multipart from '..' 5 | import type { AwsS3Part } from '..' 6 | 7 | { 8 | const uppy = new Uppy() 9 | uppy.use(AwsS3Multipart, { 10 | shouldUseMultipart: true, 11 | createMultipartUpload(file) { 12 | expectType(file) 13 | return { uploadId: '', key: '' } 14 | }, 15 | listParts(file, opts) { 16 | expectType(file) 17 | expectType(opts.uploadId) 18 | expectType(opts.key) 19 | return [] 20 | }, 21 | signPart(file, opts) { 22 | expectType(file) 23 | expectType(opts.uploadId) 24 | expectType(opts.key) 25 | expectType(opts.body) 26 | expectType(opts.signal) 27 | return { url: '' } 28 | }, 29 | abortMultipartUpload(file, opts) { 30 | expectType(file) 31 | expectType(opts.uploadId) 32 | expectType(opts.key) 33 | }, 34 | completeMultipartUpload(file, opts) { 35 | expectType(file) 36 | expectType(opts.uploadId) 37 | expectType(opts.key) 38 | expectType(opts.parts[0]) 39 | return {} 40 | }, 41 | }) 42 | } 43 | 44 | { 45 | const uppy = new Uppy() 46 | expectError(uppy.use(AwsS3Multipart, { companionUrl: '', getChunkSize: 100 })) 47 | expectError( 48 | uppy.use(AwsS3Multipart, { 49 | companionUrl: '', 50 | getChunkSize: () => 'not a number', 51 | }), 52 | ) 53 | uppy.use(AwsS3Multipart, { companionUrl: '', getChunkSize: () => 100 }) 54 | uppy.use(AwsS3Multipart, { 55 | companionUrl: '', 56 | getChunkSize: (file) => file.size, 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/README.md: -------------------------------------------------------------------------------- 1 | # @uppy/aws-s3-multipart 2 | 3 | Uppy logo: a smiling puppy above a pink upwards arrow 4 | 5 | [![npm version](https://img.shields.io/npm/v/@uppy/aws-s3-multipart.svg?style=flat-square)](https://www.npmjs.com/package/@uppy/aws-s3-multipart) 6 | ![CI status for Uppy tests](https://github.com/transloadit/uppy/workflows/Tests/badge.svg) 7 | ![CI status for Companion tests](https://github.com/transloadit/uppy/workflows/Companion/badge.svg) 8 | ![CI status for browser tests](https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg) 9 | 10 | The AwsS3Multipart plugin can be used to upload files directly to an S3 bucket using S3’s Multipart upload strategy. With this strategy, files are chopped up in parts of 5MB+ each, so they can be uploaded concurrently. It’s also reliable: if a single part fails to upload, only that 5MB has to be retried. 11 | 12 | Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service. 13 | 14 | ## Example 15 | 16 | ```js 17 | import Uppy from '@uppy/core' 18 | import AwsS3Multipart from '@uppy/aws-s3-multipart' 19 | 20 | const uppy = new Uppy() 21 | uppy.use(AwsS3Multipart, { 22 | limit: 2, 23 | companionUrl: 'https://companion.myapp.com/', 24 | }) 25 | ``` 26 | 27 | ## Installation 28 | 29 | ```bash 30 | $ npm install @uppy/aws-s3-multipart 31 | ``` 32 | 33 | Alternatively, you can also use this plugin in a pre-built bundle from Transloadit’s CDN: Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` object. See the [main Uppy documentation](https://uppy.io/docs/#Installation) for instructions. 34 | 35 | ## Documentation 36 | 37 | Documentation for this plugin can be found on the [Uppy website](https://uppy.io/docs/aws-s3-multipart). 38 | 39 | ## License 40 | 41 | [The MIT License](./LICENSE). 42 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/README.md: -------------------------------------------------------------------------------- 1 | # @uppy/companion-client 2 | 3 | Uppy logo: a smiling puppy above a pink upwards arrow 4 | 5 | [![npm version](https://img.shields.io/npm/v/@uppy/companion-client.svg?style=flat-square)](https://www.npmjs.com/package/@uppy/companion-client) 6 | ![CI status for Uppy tests](https://github.com/transloadit/uppy/workflows/Tests/badge.svg) 7 | ![CI status for Companion tests](https://github.com/transloadit/uppy/workflows/Companion/badge.svg) 8 | ![CI status for browser tests](https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg) 9 | 10 | Client library for communication with Companion. Intended for use in Uppy plugins. 11 | 12 | Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service. 13 | 14 | ## Example 15 | 16 | ```js 17 | import Uppy from '@uppy/core' 18 | import { Provider, RequestClient, Socket } from '@uppy/companion-client' 19 | 20 | const uppy = new Uppy() 21 | 22 | const client = new RequestClient(uppy, { companionUrl: 'https://uppy.mywebsite.com/' }) 23 | client.get('/drive/list').then(() => {}) 24 | 25 | const provider = new Provider(uppy, { 26 | companionUrl: 'https://uppy.mywebsite.com/', 27 | provider: providerPluginInstance, 28 | }) 29 | provider.checkAuth().then(() => {}) 30 | 31 | const socket = new Socket({ target: 'wss://uppy.mywebsite.com/' }) 32 | socket.on('progress', () => {}) 33 | ``` 34 | 35 | ## Installation 36 | 37 | > Unless you are writing a custom provider plugin, you do not need to install this. 38 | 39 | ```bash 40 | $ npm install @uppy/companion-client 41 | ``` 42 | 43 | 48 | 49 | ## License 50 | 51 | [The MIT License](./LICENSE). 52 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/README.md: -------------------------------------------------------------------------------- 1 | # @ImageTestVarLang/box 2 | 3 | ImageTestVarLang logo: a smiling pImageTestVarLang above a pink upwards arrow 4 | 5 | [![npm version](https://img.shields.io/npm/v/@ImageTestVarLang/box.svg?style=flat-square)](https://www.npmjs.com/package/@ImageTestVarLang/box) 6 | ![CI status for ImageTestVarLang tests](https://github.com/transloadit/ImageTestVarLang/workflows/Tests/badge.svg) 7 | ![CI status for Companion tests](https://github.com/transloadit/ImageTestVarLang/workflows/Companion/badge.svg) 8 | ![CI status for browser tests](https://github.com/transloadit/ImageTestVarLang/workflows/End-to-end%20tests/badge.svg) 9 | 10 | The Box plugin for ImageTestVarLang lets users import files from their Box account. 11 | 12 | A Companion instance is required for the Box plugin to work. Companion handles authentication with Box, downloads files from Box and uploads them to the destination. This saves the user bandwidth, especially helpful if they are on a mobile connection. 13 | 14 | ImageTestVarLang is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service. 15 | 16 | ## Example 17 | 18 | ```js 19 | import ImageTestVarLang from '@ImageTestVarLang/core' 20 | import Box from '@ImageTestVarLang/box' 21 | 22 | const ImageTestVarLang = new ImageTestVarLang() 23 | ImageTestVarLang.use(Box, { 24 | // Options 25 | }) 26 | ``` 27 | 28 | ## Installation 29 | 30 | ```bash 31 | $ npm install @ImageTestVarLang/box 32 | ``` 33 | 34 | Alternatively, you can also use this plugin in a pre-built bundle from Transloadit’s CDN: Edgly. In that case `ImageTestVarLang` will attach itself to the global `window.ImageTestVarLang` object. See the [main ImageTestVarLang documentation](https://ImageTestVarLang.io/docs/#Installation) for instructions. 35 | 36 | ## Documentation 37 | 38 | Documentation for this plugin can be found on the [ImageTestVarLang website](https://ImageTestVarLang.io/docs/box). 39 | 40 | ## License 41 | 42 | [The MIT License](./LICENSE). 43 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/Socket.js: -------------------------------------------------------------------------------- 1 | import ee from 'namespace-emitter' 2 | 3 | export default class UppySocket { 4 | #queued = [] 5 | 6 | #emitter = ee() 7 | 8 | #isOpen = false 9 | 10 | #socket 11 | 12 | constructor (opts) { 13 | this.opts = opts 14 | 15 | if (!opts || opts.autoOpen !== false) { 16 | this.open() 17 | } 18 | } 19 | 20 | get isOpen () { return this.#isOpen } 21 | 22 | [Symbol.for('uppy test: getSocket')] () { return this.#socket } 23 | 24 | [Symbol.for('uppy test: getQueued')] () { return this.#queued } 25 | 26 | open () { 27 | if (this.#socket != null) return 28 | 29 | this.#socket = new WebSocket(this.opts.target) 30 | 31 | this.#socket.onopen = () => { 32 | this.#isOpen = true 33 | 34 | while (this.#queued.length > 0 && this.#isOpen) { 35 | const first = this.#queued.shift() 36 | this.send(first.action, first.payload) 37 | } 38 | } 39 | 40 | this.#socket.onclose = () => { 41 | this.#isOpen = false 42 | this.#socket = null 43 | } 44 | 45 | this.#socket.onmessage = this.#handleMessage 46 | } 47 | 48 | close () { 49 | this.#socket?.close() 50 | } 51 | 52 | send (action, payload) { 53 | // attach uuid 54 | 55 | if (!this.#isOpen) { 56 | this.#queued.push({ action, payload }) 57 | return 58 | } 59 | 60 | this.#socket.send(JSON.stringify({ 61 | action, 62 | payload, 63 | })) 64 | } 65 | 66 | on (action, handler) { 67 | this.#emitter.on(action, handler) 68 | } 69 | 70 | emit (action, payload) { 71 | this.#emitter.emit(action, payload) 72 | } 73 | 74 | once (action, handler) { 75 | this.#emitter.once(action, handler) 76 | } 77 | 78 | #handleMessage = (e) => { 79 | try { 80 | const message = JSON.parse(e.data) 81 | this.emit(message.action, message.payload) 82 | } catch (err) { 83 | // TODO: use a more robust error handler. 84 | console.log(err) // eslint-disable-line no-console 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/src/index.test.js: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, it } from 'vitest' 2 | import 'whatwg-fetch' 3 | import Core from '@uppy/core' 4 | import AwsS3 from './index.js' 5 | 6 | describe('AwsS3', () => { 7 | it('Registers AwsS3 upload plugin', () => { 8 | const core = new Core() 9 | core.use(AwsS3) 10 | 11 | const pluginNames = core[Symbol.for('uppy test: getPlugins')]('uploader').map((plugin) => plugin.constructor.name) 12 | expect(pluginNames).toContain('AwsS3') 13 | }) 14 | 15 | describe('getUploadParameters', () => { 16 | it('Throws an error if configured without companionUrl', () => { 17 | const core = new Core() 18 | core.use(AwsS3) 19 | const awsS3 = core.getPlugin('AwsS3') 20 | 21 | expect(awsS3.opts.getUploadParameters).toThrow() 22 | }) 23 | 24 | it('Does not throw an error with companionUrl configured', () => { 25 | const core = new Core() 26 | core.use(AwsS3, { companionUrl: 'https://companion.uppy.io/' }) 27 | const awsS3 = core.getPlugin('AwsS3') 28 | const file = { 29 | meta: { 30 | name: 'foo.jpg', 31 | type: 'image/jpg', 32 | }, 33 | } 34 | 35 | expect(() => awsS3.opts.getUploadParameters(file)).not.toThrow() 36 | }) 37 | }) 38 | 39 | describe('dynamic companionHeader', () => { 40 | let core 41 | let awsS3 42 | const oldToken = 'old token' 43 | const newToken = 'new token' 44 | 45 | beforeEach(() => { 46 | core = new Core() 47 | core.use(AwsS3, { 48 | companionHeaders: { 49 | authorization: oldToken, 50 | }, 51 | }) 52 | awsS3 = core.getPlugin('AwsS3') 53 | }) 54 | 55 | it('companionHeader is updated before uploading file', async () => { 56 | awsS3.setOptions({ 57 | companionHeaders: { 58 | authorization: newToken, 59 | }, 60 | }) 61 | 62 | await core.upload() 63 | 64 | const client = awsS3[Symbol.for('uppy test: getClient')]() 65 | 66 | expect(client[Symbol.for('uppy test: getCompanionHeaders')]().authorization).toEqual(newToken) 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { Uppy, type UppyFile } from '@uppy/core' 2 | import { expectType, expectError } from 'tsd' 3 | import type { AwsS3Part } from '@uppy/aws-s3-multipart' 4 | import AwsS3 from '..' 5 | 6 | { 7 | const uppy = new Uppy() 8 | uppy.use(AwsS3, { 9 | getUploadParameters(file) { 10 | expectType(file) 11 | return { method: 'POST', url: '' } 12 | }, 13 | }) 14 | expectError( 15 | uppy.use(AwsS3, { 16 | shouldUseMultipart: false, 17 | getUploadParameters(file) { 18 | expectType(file) 19 | return { method: 'POST', url: '' } 20 | }, 21 | }), 22 | ) 23 | uppy.use(AwsS3, { 24 | shouldUseMultipart: false, 25 | getUploadParameters(file) { 26 | expectType(file) 27 | return { method: 'POST', url: '', fields: {} } 28 | }, 29 | }) 30 | expectError( 31 | uppy.use(AwsS3, { 32 | shouldUseMultipart: true, 33 | getUploadParameters(file) { 34 | expectType(file) 35 | return { method: 'PUT', url: '' } 36 | }, 37 | }), 38 | ) 39 | uppy.use(AwsS3, { 40 | shouldUseMultipart: () => Math.random() > 0.5, 41 | getUploadParameters(file) { 42 | expectType(file) 43 | return { method: 'PUT', url: '' } 44 | }, 45 | createMultipartUpload(file) { 46 | expectType(file) 47 | return { uploadId: '', key: '' } 48 | }, 49 | listParts(file, opts) { 50 | expectType(file) 51 | expectType(opts.uploadId) 52 | expectType(opts.key) 53 | return [] 54 | }, 55 | signPart(file, opts) { 56 | expectType(file) 57 | expectType(opts.uploadId) 58 | expectType(opts.key) 59 | expectType(opts.body) 60 | expectType(opts.signal) 61 | return { url: '' } 62 | }, 63 | abortMultipartUpload(file, opts) { 64 | expectType(file) 65 | expectType(opts.uploadId) 66 | expectType(opts.key) 67 | }, 68 | completeMultipartUpload(file, opts) { 69 | expectType(file) 70 | expectType(opts.uploadId) 71 | expectType(opts.key) 72 | expectType(opts.parts[0]) 73 | return {} 74 | }, 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/src/isXml.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import isXml from './isXml.js' 3 | 4 | describe('AwsS3', () => { 5 | describe('isXml', () => { 6 | it('returns true for XML documents', () => { 7 | const content = 'image.jpg' 8 | expect(isXml(content, { 9 | getResponseHeader: () => 'application/xml', 10 | })).toEqual(true) 11 | expect(isXml(content, { 12 | getResponseHeader: () => 'text/xml', 13 | })).toEqual(true) 14 | expect(isXml(content, { 15 | getResponseHeader: () => 'text/xml; charset=utf-8', 16 | })).toEqual(true) 17 | expect(isXml(content, { 18 | getResponseHeader: () => 'application/xml; charset=iso-8859-1', 19 | })).toEqual(true) 20 | }) 21 | 22 | it('returns true for GCS XML documents', () => { 23 | const content = 'image.jpg' 24 | expect(isXml(content, { 25 | getResponseHeader: () => 'text/html', 26 | })).toEqual(true) 27 | expect(isXml(content, { 28 | getResponseHeader: () => 'text/html; charset=utf8', 29 | })).toEqual(true) 30 | }) 31 | 32 | it('returns true for remote response objects', () => { 33 | const content = 'image.jpg' 34 | expect(isXml(content, { 35 | headers: { 'content-type': 'application/xml' }, 36 | })).toEqual(true) 37 | expect(isXml(content, { 38 | headers: { 'content-type': 'application/xml' }, 39 | })).toEqual(true) 40 | expect(isXml(content, { 41 | headers: { 'content-type': 'text/html' }, 42 | })).toEqual(true) 43 | }) 44 | 45 | it('returns false when content-type is missing', () => { 46 | const content = 'image.jpg' 47 | expect(isXml(content, { 48 | getResponseHeader: () => null, 49 | })).toEqual(false) 50 | expect(isXml(content, { 51 | headers: { 'content-type': null }, 52 | })).toEqual(false) 53 | expect(isXml(content, { 54 | headers: {}, 55 | })).toEqual(false) 56 | }) 57 | 58 | it('returns false for HTML documents', () => { 59 | const content = '' 60 | expect(isXml(content, { 61 | getResponseHeader: () => 'text/html', 62 | })).toEqual(false) 63 | }) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @uppy/audio 2 | 3 | ## 1.0.4 4 | 5 | Released: 2023-02-13 6 | Included in: Uppy v3.5.0 7 | 8 | - @uppy/audio,@uppy/core,@uppy/dashboard,@uppy/screen-capture: Warn more instead of erroring (Artur Paikin / #4302) 9 | 10 | ## 1.0.3 11 | 12 | Released: 2023-01-26 13 | Included in: Uppy v3.4.0 14 | 15 | - @uppy/audio: @uppy/audio fix typo in readme (elliotsayes / #4240) 16 | 17 | ## 1.0.2 18 | 19 | Released: 2022-09-25 20 | Included in: Uppy v3.1.0 21 | 22 | - @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) 23 | 24 | ## 1.0.0 25 | 26 | Released: 2022-08-22 27 | Included in: Uppy v3.0.0 28 | 29 | - Switch to ESM 30 | 31 | ## 0.3.2 32 | 33 | Released: 2022-05-30 34 | Included in: Uppy v2.11.0 35 | 36 | - @uppy/angular,@uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/onedrive,@uppy/progress-bar,@uppy/react,@uppy/redux-dev-tools,@uppy/robodog,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: doc: update bundler recommendation (Antoine du Hamel / #3763) 37 | 38 | ## 0.3.1 39 | 40 | Released: 2022-05-14 41 | Included in: Uppy v2.10.0 42 | 43 | - @uppy/audio: fix types (Merlijn Vos / #3689) 44 | 45 | ## 0.3.0 46 | 47 | Released: 2022-03-16 48 | Included in: Uppy v2.8.0 49 | 50 | - @uppy/audio: refactor to ESM (Antoine du Hamel / #3470) 51 | 52 | ## 0.2.1 53 | 54 | Released: 2021-12-09 55 | Included in: Uppy v2.3.1 56 | 57 | - @uppy/audio: showRecordingLength option was removed, always clearInterval (Artur Paikin / #3351) 58 | 59 | ## 0.2.0 60 | 61 | Released: 2021-12-07 62 | Included in: Uppy v2.3.0 63 | 64 | - @uppy/audio: new @uppy/audio plugin for recording with microphone (Artur Paikin / #2976) 65 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/src/Box.jsx: -------------------------------------------------------------------------------- 1 | import { UIPlugin } from '@ImageTestVarLang/core' 2 | import { Provider } from '@ImageTestVarLang/companion-client' 3 | import { ProviderViews } from '@ImageTestVarLang/provider-views' 4 | import { h } from 'preact' 5 | 6 | import locale from './locale.js' 7 | import packageJson from '../package.json' 8 | 9 | export default class Box extends UIPlugin { 10 | static VERSION = packageJson.version 11 | 12 | constructor (ImageTestVarLang, opts) { 13 | super(ImageTestVarLang, opts) 14 | this.id = this.opts.id || 'Box' 15 | Provider.initPlugin(this, opts) 16 | this.title = this.opts.title || 'Box' 17 | this.icon = () => ( 18 | 24 | ) 25 | 26 | this.provider = new Provider(ImageTestVarLang, { 27 | companionUrl: this.opts.companionUrl, 28 | companionHeaders: this.opts.companionHeaders, 29 | companionKeysParams: this.opts.companionKeysParams, 30 | companionCookiesRule: this.opts.companionCookiesRule, 31 | provider: 'box', 32 | pluginId: this.id, 33 | supportsRefreshToken: false, 34 | }) 35 | 36 | this.defaultLocale = locale 37 | 38 | this.i18nInit() 39 | this.title = this.i18n('pluginNameBox') 40 | 41 | this.onFirstRender = this.onFirstRender.bind(this) 42 | this.render = this.render.bind(this) 43 | } 44 | 45 | install () { 46 | this.view = new ProviderViews(this, { 47 | provider: this.provider, 48 | loadAllFiles: true, 49 | }) 50 | 51 | const { target } = this.opts 52 | if (target) { 53 | this.mount(target, this) 54 | } 55 | } 56 | 57 | uninstall () { 58 | this.view.tearDown() 59 | this.unmount() 60 | } 61 | 62 | onFirstRender () { 63 | return this.view.getFolder() 64 | } 65 | 66 | render (state) { 67 | return this.view.render(state) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/audio-oscilloscope/index.js: -------------------------------------------------------------------------------- 1 | function isFunction (v) { 2 | return typeof v === 'function' 3 | } 4 | 5 | function result (v) { 6 | return isFunction(v) ? v() : v 7 | } 8 | 9 | /* Audio Oscilloscope 10 | https://github.com/miguelmota/audio-oscilloscope 11 | */ 12 | export default class AudioOscilloscope { 13 | constructor (canvas, options = {}) { 14 | const canvasOptions = options.canvas || {} 15 | const canvasContextOptions = options.canvasContext || {} 16 | this.analyser = null 17 | this.bufferLength = 0 18 | this.dataArray = [] 19 | this.canvas = canvas 20 | this.width = result(canvasOptions.width) || this.canvas.width 21 | this.height = result(canvasOptions.height) || this.canvas.height 22 | this.canvas.width = this.width 23 | this.canvas.height = this.height 24 | this.canvasContext = this.canvas.getContext('2d') 25 | this.canvasContext.fillStyle = result(canvasContextOptions.fillStyle) || 'rgb(255, 255, 255)' 26 | this.canvasContext.strokeStyle = result(canvasContextOptions.strokeStyle) || 'rgb(0, 0, 0)' 27 | this.canvasContext.lineWidth = result(canvasContextOptions.lineWidth) || 1 28 | this.onDrawFrame = isFunction(options.onDrawFrame) ? options.onDrawFrame : () => {} 29 | } 30 | 31 | addSource (streamSource) { 32 | this.streamSource = streamSource 33 | this.audioContext = this.streamSource.context 34 | this.analyser = this.audioContext.createAnalyser() 35 | this.analyser.fftSize = 2048 36 | this.bufferLength = this.analyser.frequencyBinCount 37 | this.source = this.audioContext.createBufferSource() 38 | this.dataArray = new Uint8Array(this.bufferLength) 39 | this.analyser.getByteTimeDomainData(this.dataArray) 40 | this.streamSource.connect(this.analyser) 41 | } 42 | 43 | draw () { 44 | const { analyser, dataArray, bufferLength } = this 45 | const ctx = this.canvasContext 46 | const w = this.width 47 | const h = this.height 48 | 49 | if (analyser) { 50 | analyser.getByteTimeDomainData(dataArray) 51 | } 52 | 53 | ctx.fillRect(0, 0, w, h) 54 | ctx.beginPath() 55 | 56 | const sliceWidth = (w * 1.0) / bufferLength 57 | let x = 0 58 | 59 | if (!bufferLength) { 60 | ctx.moveTo(0, this.height / 2) 61 | } 62 | 63 | for (let i = 0; i < bufferLength; i++) { 64 | const v = dataArray[i] / 128.0 65 | const y = v * (h / 2) 66 | 67 | if (i === 0) { 68 | ctx.moveTo(x, y) 69 | } else { 70 | ctx.lineTo(x, y) 71 | } 72 | 73 | x += sliceWidth 74 | } 75 | 76 | ctx.lineTo(w, h / 2) 77 | ctx.stroke() 78 | 79 | this.onDrawFrame(this) 80 | requestAnimationFrame(this.#draw) 81 | } 82 | 83 | #draw = () => this.draw() 84 | } 85 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/src/createSignedURL.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, afterEach } from 'vitest' 2 | import assert from 'node:assert' 3 | import { S3Client, UploadPartCommand, PutObjectCommand } from '@aws-sdk/client-s3' 4 | import { getSignedUrl } from '@aws-sdk/s3-request-presigner' 5 | import createSignedURL from './createSignedURL.js' 6 | 7 | const bucketName = 'some-bucket' 8 | const s3ClientOptions = { 9 | region: 'us-bar-1', 10 | credentials: { 11 | accessKeyId: 'foo', 12 | secretAccessKey: 'bar', 13 | sessionToken: 'foobar', 14 | }, 15 | } 16 | const { Date: OriginalDate } = globalThis 17 | 18 | describe('createSignedURL', () => { 19 | beforeEach(() => { 20 | const now_ms = OriginalDate.now() 21 | globalThis.Date = function Date () { 22 | if (new.target) { 23 | return Reflect.construct(OriginalDate, [now_ms]) 24 | } 25 | return Reflect.apply(OriginalDate, this, [now_ms]) 26 | } 27 | globalThis.Date.now = function now () { 28 | return now_ms 29 | } 30 | }) 31 | afterEach(() => { 32 | globalThis.Date = OriginalDate 33 | }) 34 | it('should be able to sign non-multipart upload', async () => { 35 | const client = new S3Client(s3ClientOptions) 36 | assert.strictEqual( 37 | (await createSignedURL({ 38 | accountKey: s3ClientOptions.credentials.accessKeyId, 39 | accountSecret: s3ClientOptions.credentials.secretAccessKey, 40 | sessionToken: s3ClientOptions.credentials.sessionToken, 41 | bucketName, 42 | Key: 'some/key', 43 | Region: s3ClientOptions.region, 44 | expires: 900, 45 | })).searchParams.get('X-Amz-Signature'), 46 | new URL(await getSignedUrl(client, new PutObjectCommand({ 47 | Bucket: bucketName, 48 | Fields: {}, 49 | Key: 'some/key', 50 | }, { expiresIn: 900 }))).searchParams.get('X-Amz-Signature'), 51 | ) 52 | }) 53 | it('should be able to sign multipart upload', async () => { 54 | const client = new S3Client(s3ClientOptions) 55 | const partNumber = 99 56 | const uploadId = 'dummyUploadId' 57 | assert.strictEqual( 58 | (await createSignedURL({ 59 | accountKey: s3ClientOptions.credentials.accessKeyId, 60 | accountSecret: s3ClientOptions.credentials.secretAccessKey, 61 | sessionToken: s3ClientOptions.credentials.sessionToken, 62 | uploadId, 63 | partNumber, 64 | bucketName, 65 | Key: 'some/key', 66 | Region: s3ClientOptions.region, 67 | expires: 900, 68 | })).searchParams.get('X-Amz-Signature'), 69 | new URL(await getSignedUrl(client, new UploadPartCommand({ 70 | Bucket: bucketName, 71 | UploadId: uploadId, 72 | PartNumber: partNumber, 73 | Key: 'some/key', 74 | }, { expiresIn: 900 }))).searchParams.get('X-Amz-Signature'), 75 | ) 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Clone this file to `.env` and edit the clone. 2 | 3 | NODE_ENV=development 4 | 5 | # Companion 6 | # ======================= 7 | COMPANION_DATADIR=./output 8 | COMPANION_DOMAIN=localhost:3020 9 | COMPANION_PROTOCOL=http 10 | COMPANION_PORT=3020 11 | COMPANION_CLIENT_ORIGINS= 12 | COMPANION_SECRET=development 13 | COMPANION_PREAUTH_SECRET=development2 14 | 15 | # NOTE: Only enable this in development. Enabling it in production is a security risk 16 | COMPANION_ALLOW_LOCAL_URLS=true 17 | 18 | # to enable S3 19 | COMPANION_AWS_KEY="YOUR AWS KEY" 20 | COMPANION_AWS_SECRET="YOUR AWS SECRET" 21 | # specifying a secret file will override a directly set secret 22 | # COMPANION_AWS_SECRET_FILE="PATH/TO/AWS/SECRET/FILE" 23 | COMPANION_AWS_BUCKET="YOUR AWS S3 BUCKET" 24 | COMPANION_AWS_REGION="AWS REGION" 25 | COMPANION_AWS_PREFIX="OPTIONAL PREFIX" 26 | # to enable S3 Transfer Acceleration (default: false) 27 | # COMPANION_AWS_USE_ACCELERATE_ENDPOINT="false" 28 | # to set X-Amz-Expires query param in presigned urls (in seconds, default: 800) 29 | # COMPANION_AWS_EXPIRES="800" 30 | # to set a canned ACL for uploaded objects: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl 31 | # COMPANION_AWS_ACL="public-read" 32 | 33 | COMPANION_BOX_KEY=*** 34 | COMPANION_BOX_SECRET=*** 35 | 36 | COMPANION_DROPBOX_KEY=*** 37 | COMPANION_DROPBOX_SECRET=*** 38 | 39 | COMPANION_GOOGLE_KEY=*** 40 | COMPANION_GOOGLE_SECRET=*** 41 | 42 | COMPANION_INSTAGRAM_KEY=*** 43 | COMPANION_INSTAGRAM_SECRET=*** 44 | 45 | COMPANION_FACEBOOK_KEY=*** 46 | COMPANION_FACEBOOK_SECRET=*** 47 | 48 | COMPANION_ZOOM_KEY=*** 49 | COMPANION_ZOOM_SECRET=*** 50 | 51 | COMPANION_UNSPLASH_KEY=*** 52 | COMPANION_UNSPLASH_SECRET=*** 53 | 54 | COMPANION_ONEDRIVE_KEY=*** 55 | COMPANION_ONEDRIVE_SECRET=**** 56 | 57 | # To test dynamic Oauth against local companion (which is pointless but allows us to test it without Transloadit's servers), enable these: 58 | #COMPANION_GOOGLE_KEYS_ENDPOINT=http://localhost:3020/drive/test-dynamic-oauth-credentials?secret=development 59 | #COMPANION_TEST_DYNAMIC_OAUTH_CREDENTIALS=true 60 | #COMPANION_TEST_DYNAMIC_OAUTH_CREDENTIALS_SECRET=development 61 | 62 | 63 | # Development environment 64 | # ======================= 65 | 66 | VITE_UPLOADER=tus 67 | # VITE_UPLOADER=s3 68 | # VITE_UPLOADER=s3-multipart 69 | # xhr will use protocol 'multipart' in companion, if used with a remote service, e.g. google drive. 70 | # If local upload will use browser XHR 71 | # VITE_UPLOADER=xhr 72 | # VITE_UPLOADER=transloadit 73 | # VITE_UPLOADER=transloadit-s3 74 | # VITE_UPLOADER=transloadit-xhr 75 | 76 | VITE_COMPANION_URL=http://localhost:3020 77 | # See also Transloadit.COMPANION_PATTERN 78 | VITE_COMPANION_ALLOWED_HOSTS="\.transloadit\.com$" 79 | VITE_TUS_ENDPOINT=https://tusd.tusdemo.net/files/ 80 | VITE_XHR_ENDPOINT=https://xhr-server.herokuapp.com/upload 81 | 82 | # If you want to test dynamic Oauth 83 | # VITE_COMPANION_GOOGLE_DRIVE_KEYS_PARAMS_CREDENTIALS_NAME=companion-google-drive 84 | 85 | VITE_TRANSLOADIT_KEY=*** 86 | VITE_TRANSLOADIT_TEMPLATE=*** 87 | VITE_TRANSLOADIT_SERVICE_URL=https://api2.transloadit.com 88 | # Fill in if you want requests sent to Transloadit to be signed: 89 | # VITE_TRANSLOADIT_SECRET=*** 90 | -------------------------------------------------------------------------------- /BUNDLE-README.md: -------------------------------------------------------------------------------- 1 | # ImageTestVarLang 2 | 3 | Note that the recommended way to use ImageTestVarLang is to install it with yarn/npm and use a 4 | bundler like Webpack so that you can create a smaller custom build with only the 5 | things that you need. More info on . 6 | 7 | ## How to use this bundle 8 | 9 | You can extract the contents of this zip to directory, such as `./js/ImageTestVarLang`. 10 | 11 | create an HTML file, for example `./start.html`, with the following contents: 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 |
23 |
Uploaded files:
24 |
    25 |
    26 | 27 | 28 | 56 | ``` 57 | 58 | Now open `start.html` in your browser, and the ImageTestVarLang Dashboard will appear. 59 | 60 | ## Next steps 61 | 62 | In the example you built, ImageTestVarLang uploads to a demo server shortly after uploading. 63 | You’ll want to target your own tusd server, S3 bucket, or Nginx/Apache server. For the latter, use the Xhr plugin: which uploads using regular multipart form posts, that you’ll existing Ruby or PHP backend will be able to make sense of, as if a `` had been used. 64 | 65 | The Dashboard now opens when clicking the button, but you can also draw it inline into the page. This, and many more configuration options can be found here: . 66 | 67 | ImageTestVarLang has many more Plugins besides Xhr and the Dashboard. For example, you can enable Webcam, Instagram, or video encoding support. For a full list of Plugins check here: . 68 | 69 | Note that for some Plugins, you will need to run a server side component called: Companion. Those plugins are marked with a (c) symbol. Alternatively, you can sign up for a free Transloadit account. Transloadit runs Companion for you, tusd servers to handle resumable file uploads, and can post-process files to scan for viruses, recognize faces, etc. Check: . 70 | 71 | 72 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @uppy/companion-client 2 | 3 | ## 3.6.1 4 | 5 | Released: 2023-11-24 6 | Included in: Uppy v3.20.0 7 | 8 | - @uppy/companion-client: fix log type error (Mikael Finstad / #4766) 9 | - @uppy/companion-client: revert breaking change (Antoine du Hamel / #4801) 10 | 11 | ## 3.5.0 12 | 13 | Released: 2023-10-20 14 | Included in: Uppy v3.18.0 15 | 16 | - @uppy/companion-client: fixup! Added Companion OAuth Key type (Murderlon / #4668) 17 | - @uppy/companion-client: Added Companion OAuth Key type (Chris Pratt / #4668) 18 | 19 | ## 3.4.1 20 | 21 | Released: 2023-09-29 22 | Included in: Uppy v3.17.0 23 | 24 | - @uppy/companion-client: fix a refresh token race condition (Mikael Finstad / #4695) 25 | 26 | ## 3.4.0 27 | 28 | Released: 2023-09-05 29 | Included in: Uppy v3.15.0 30 | 31 | - @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) 32 | 33 | ## 3.3.0 34 | 35 | Released: 2023-08-15 36 | Included in: Uppy v3.14.0 37 | 38 | - @uppy/companion-client,@uppy/provider-views: make authentication optional (Dominik Schmidt / #4556) 39 | 40 | ## 3.1.2 41 | 42 | Released: 2023-04-04 43 | Included in: Uppy v3.7.0 44 | 45 | - @uppy/companion-client: do not open socket more than once (Artur Paikin) 46 | 47 | ## 3.1.1 48 | 49 | Released: 2022-11-16 50 | Included in: Uppy v3.3.1 51 | 52 | - @uppy/companion-client: treat `*` the same as missing header (Antoine du Hamel / #4221) 53 | 54 | ## 3.1.0 55 | 56 | Released: 2022-11-10 57 | Included in: Uppy v3.3.0 58 | 59 | - @uppy/companion-client: add support for `AbortSignal` (Antoine du Hamel / #4201) 60 | - @uppy/companion-client: prevent preflight race condition (Mikael Finstad / #4182) 61 | 62 | ## 3.0.2 63 | 64 | Released: 2022-09-25 65 | Included in: Uppy v3.1.0 66 | 67 | - @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) 68 | 69 | ## 3.0.0 70 | 71 | Released: 2022-08-22 72 | Included in: Uppy v3.0.0 73 | 74 | - Switch to ESM 75 | 76 | ## 2.2.0 77 | 78 | Released: 2022-05-30 79 | Included in: Uppy v2.11.0 80 | 81 | - @uppy/companion-client: Revert "Revert "@uppy/companion-client: refactor to ESM"" (Antoine du Hamel / #3730) 82 | 83 | ## 2.1.0 84 | 85 | Released: 2022-05-14 86 | Included in: Uppy v2.10.0 87 | 88 | - @uppy/companion-client: refactor to ESM (Antoine du Hamel / #3693) 89 | 90 | ## 2.0.6 91 | 92 | Released: 2022-04-07 93 | Included in: Uppy v2.9.2 94 | 95 | - @uppy/aws-s3,@uppy/companion-client,@uppy/transloadit,@uppy/utils: Propagate `isNetworkError` through error wrappers (Renée Kooi / #3620) 96 | 97 | ## 2.0.5 98 | 99 | Released: 2022-02-14 100 | Included in: Uppy v2.5.0 101 | 102 | - @uppy/companion-client,@uppy/companion,@uppy/provider-views,@uppy/robodog: Finishing touches on Companion dynamic Oauth (Renée Kooi / #2802) 103 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { Uppy } from '@uppy/core' 2 | 3 | /** 4 | * Async storage interface, similar to `localStorage`. This can be used to 5 | * implement custom storages for authentication tokens. 6 | */ 7 | export interface TokenStorage { 8 | setItem: (key: string, value: string) => Promise 9 | getItem: (key: string) => Promise 10 | removeItem: (key: string) => Promise 11 | } 12 | 13 | type CompanionHeaders = Record 14 | 15 | type CompanionKeys = { 16 | key: string 17 | credentialsName: string 18 | } 19 | 20 | export interface RequestClientOptions { 21 | companionUrl: string 22 | companionHeaders?: CompanionHeaders 23 | companionCookiesRule?: RequestCredentials 24 | companionKeysParams?: CompanionKeys 25 | } 26 | 27 | type RequestOptions = { 28 | skipPostResponse?: boolean 29 | signal?: AbortSignal 30 | } 31 | 32 | export class RequestClient { 33 | constructor(uppy: Uppy, opts: RequestClientOptions) 34 | 35 | readonly hostname: string 36 | 37 | setCompanionHeaders(headers: CompanionHeaders): void 38 | 39 | get(path: string, options?: RequestOptions): Promise 40 | 41 | /** @deprecated use option bag instead */ 42 | get(path: string, skipPostResponse: boolean): Promise 43 | 44 | post( 45 | path: string, 46 | data: Record, 47 | options?: RequestOptions, 48 | ): Promise 49 | 50 | /** @deprecated use option bag instead */ 51 | post( 52 | path: string, 53 | data: Record, 54 | skipPostResponse: boolean, 55 | ): Promise 56 | 57 | delete( 58 | path: string, 59 | data?: Record, 60 | options?: RequestOptions, 61 | ): Promise 62 | 63 | /** @deprecated use option bag instead */ 64 | delete( 65 | path: string, 66 | data: Record, 67 | skipPostResponse: boolean, 68 | ): Promise 69 | } 70 | 71 | /** 72 | * Options for Providers that can be passed in by Uppy users through 73 | * Plugin constructors. 74 | */ 75 | export interface PublicProviderOptions extends RequestClientOptions { 76 | companionAllowedHosts?: string | RegExp | Array 77 | } 78 | 79 | /** 80 | * Options for Providers, including internal options that Plugins can set. 81 | */ 82 | export interface ProviderOptions extends PublicProviderOptions { 83 | provider: string 84 | name?: string 85 | pluginId: string 86 | } 87 | 88 | export class Provider extends RequestClient { 89 | constructor(uppy: Uppy, opts: ProviderOptions) 90 | 91 | checkAuth(): Promise 92 | 93 | authUrl(): string 94 | 95 | fileUrl(id: string): string 96 | 97 | list(directory: string): Promise 98 | 99 | logout(redirect?: string): Promise 100 | 101 | static initPlugin( 102 | plugin: unknown, 103 | opts: Record, 104 | defaultOpts?: Record, 105 | ): void 106 | } 107 | 108 | export interface SocketOptions { 109 | target: string 110 | autoOpen?: boolean 111 | } 112 | 113 | export class Socket { 114 | readonly isOpen: boolean 115 | 116 | constructor(opts: SocketOptions) 117 | 118 | open(): void 119 | 120 | close(): void 121 | 122 | send(action: string, payload: unknown): void 123 | 124 | on(action: string, handler: (param: any) => void): void 125 | 126 | once(action: string, handler: (param: any) => void): void 127 | 128 | emit(action: string, payload: (param: any) => void): void 129 | } 130 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/box/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @ImageTestVarLang/box 2 | 3 | ## 2.1.2 4 | 5 | Released: 2023-07-13 6 | Included in: ImageTestVarLang v3.12.0 7 | 8 | - @ImageTestVarLang/box,@ImageTestVarLang/companion,@ImageTestVarLang/dropbox,@ImageTestVarLang/google-drive,@ImageTestVarLang/onedrive,@ImageTestVarLang/provider-views: Load Google Drive / OneDrive lists 5-10x faster & always load all files (Merlijn Vos / #4513) 9 | 10 | ## 2.0.1 11 | 12 | Released: 2022-09-25 13 | Included in: ImageTestVarLang v3.1.0 14 | 15 | - @ImageTestVarLang/audio,@ImageTestVarLang/aws-s3-multipart,@ImageTestVarLang/aws-s3,@ImageTestVarLang/box,@ImageTestVarLang/companion-client,@ImageTestVarLang/companion,@ImageTestVarLang/compressor,@ImageTestVarLang/core,@ImageTestVarLang/dashboard,@ImageTestVarLang/drag-drop,@ImageTestVarLang/drop-target,@ImageTestVarLang/dropbox,@ImageTestVarLang/facebook,@ImageTestVarLang/file-input,@ImageTestVarLang/form,@ImageTestVarLang/golden-retriever,@ImageTestVarLang/google-drive,@ImageTestVarLang/image-editor,@ImageTestVarLang/informer,@ImageTestVarLang/instagram,@ImageTestVarLang/locales,@ImageTestVarLang/onedrive,@ImageTestVarLang/progress-bar,@ImageTestVarLang/provider-views,@ImageTestVarLang/react,@ImageTestVarLang/redux-dev-tools,@ImageTestVarLang/remote-sources,@ImageTestVarLang/screen-capture,@ImageTestVarLang/status-bar,@ImageTestVarLang/store-default,@ImageTestVarLang/store-redux,@ImageTestVarLang/svelte,@ImageTestVarLang/thumbnail-generator,@ImageTestVarLang/transloadit,@ImageTestVarLang/tus,@ImageTestVarLang/unsplash,@ImageTestVarLang/url,@ImageTestVarLang/utils,@ImageTestVarLang/vue,@ImageTestVarLang/webcam,@ImageTestVarLang/xhr-upload,@ImageTestVarLang/zoom: add missing entries to changelog for individual packages (Antoine du Hamel / #4092) 16 | 17 | ## 2.0.0 18 | 19 | Released: 2022-08-22 20 | Included in: ImageTestVarLang v3.0.0 21 | 22 | - Switch to ESM 23 | 24 | ## 1.0.7 25 | 26 | Released: 2022-05-30 27 | Included in: ImageTestVarLang v2.11.0 28 | 29 | - @ImageTestVarLang/angular,@ImageTestVarLang/audio,@ImageTestVarLang/aws-s3-multipart,@ImageTestVarLang/aws-s3,@ImageTestVarLang/box,@ImageTestVarLang/core,@ImageTestVarLang/dashboard,@ImageTestVarLang/drag-drop,@ImageTestVarLang/dropbox,@ImageTestVarLang/facebook,@ImageTestVarLang/file-input,@ImageTestVarLang/form,@ImageTestVarLang/golden-retriever,@ImageTestVarLang/google-drive,@ImageTestVarLang/image-editor,@ImageTestVarLang/informer,@ImageTestVarLang/instagram,@ImageTestVarLang/onedrive,@ImageTestVarLang/progress-bar,@ImageTestVarLang/react,@ImageTestVarLang/redux-dev-tools,@ImageTestVarLang/robodog,@ImageTestVarLang/screen-capture,@ImageTestVarLang/status-bar,@ImageTestVarLang/store-default,@ImageTestVarLang/store-redux,@ImageTestVarLang/thumbnail-generator,@ImageTestVarLang/transloadit,@ImageTestVarLang/tus,@ImageTestVarLang/unsplash,@ImageTestVarLang/url,@ImageTestVarLang/vue,@ImageTestVarLang/webcam,@ImageTestVarLang/xhr-upload,@ImageTestVarLang/zoom: doc: update bundler recommendation (Antoine du Hamel / #3763) 30 | 31 | ## 1.0.6 32 | 33 | Released: 2022-04-27 34 | Included in: ImageTestVarLang v2.9.4 35 | 36 | - @ImageTestVarLang/box: refactor to ESM (Antoine du Hamel / #3643) 37 | 38 | ## 1.0.5 39 | 40 | Released: 2021-12-07 41 | Included in: ImageTestVarLang v2.3.0 42 | 43 | - @ImageTestVarLang/aws-s3,@ImageTestVarLang/box,@ImageTestVarLang/core,@ImageTestVarLang/dashboard,@ImageTestVarLang/drag-drop,@ImageTestVarLang/dropbox,@ImageTestVarLang/facebook,@ImageTestVarLang/file-input,@ImageTestVarLang/google-drive,@ImageTestVarLang/image-editor,@ImageTestVarLang/instagram,@ImageTestVarLang/locales,@ImageTestVarLang/onedrive,@ImageTestVarLang/screen-capture,@ImageTestVarLang/status-bar,@ImageTestVarLang/thumbnail-generator,@ImageTestVarLang/transloadit,@ImageTestVarLang/url,@ImageTestVarLang/webcam,@ImageTestVarLang/xhr-upload,@ImageTestVarLang/zoom: Refactor locale scripts & generate types and docs (Merlijn Vos / #3276) 44 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/RecordingScreen.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/media-has-caption */ 2 | import { h } from 'preact' 3 | import { useEffect, useRef } from 'preact/hooks' 4 | import RecordButton from './RecordButton.jsx' 5 | import RecordingLength from './RecordingLength.jsx' 6 | import AudioSourceSelect from './AudioSourceSelect.jsx' 7 | import AudioOscilloscope from './audio-oscilloscope/index.js' 8 | import SubmitButton from './SubmitButton.jsx' 9 | import DiscardButton from './DiscardButton.jsx' 10 | 11 | export default function RecordingScreen (props) { 12 | const { 13 | stream, 14 | recordedAudio, 15 | onStop, 16 | recording, 17 | supportsRecording, 18 | audioSources, 19 | showAudioSourceDropdown, 20 | onSubmit, 21 | i18n, 22 | onStartRecording, 23 | onStopRecording, 24 | onDiscardRecordedAudio, 25 | recordingLengthSeconds, 26 | } = props 27 | 28 | const canvasEl = useRef(null) 29 | const oscilloscope = useRef(null) 30 | 31 | // componentDidMount / componentDidUnmount 32 | useEffect(() => { 33 | return () => { 34 | oscilloscope.current = null 35 | onStop() 36 | } 37 | }, [onStop]) 38 | 39 | // componentDidUpdate 40 | useEffect(() => { 41 | if (!recordedAudio) { 42 | oscilloscope.current = new AudioOscilloscope(canvasEl.current, { 43 | canvas: { 44 | width: 600, 45 | height: 600, 46 | }, 47 | canvasContext: { 48 | lineWidth: 2, 49 | fillStyle: 'rgb(0,0,0)', 50 | strokeStyle: 'green', 51 | }, 52 | }) 53 | oscilloscope.current.draw() 54 | 55 | if (stream) { 56 | const audioContext = new AudioContext() 57 | const source = audioContext.createMediaStreamSource(stream) 58 | oscilloscope.current.addSource(source) 59 | } 60 | } 61 | }, [recordedAudio, stream]) 62 | 63 | const hasRecordedAudio = recordedAudio != null 64 | const shouldShowRecordButton = !hasRecordedAudio && supportsRecording 65 | const shouldShowAudioSourceDropdown = showAudioSourceDropdown 66 | && !hasRecordedAudio 67 | && audioSources 68 | && audioSources.length > 1 69 | 70 | return ( 71 |
    72 |
    73 | {hasRecordedAudio 74 | ? ( 75 |
    87 |
    88 |
    89 | {shouldShowAudioSourceDropdown 90 | ? AudioSourceSelect(props) 91 | : null} 92 |
    93 |
    94 | {shouldShowRecordButton && ( 95 | 101 | )} 102 | 103 | {hasRecordedAudio && } 104 | 105 | {hasRecordedAudio && } 106 |
    107 | 108 |
    109 | {!hasRecordedAudio && ( 110 | 111 | )} 112 |
    113 |
    114 |
    115 | ) 116 | } 117 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/style.scss: -------------------------------------------------------------------------------- 1 | @import '@uppy/core/src/_utils.scss'; 2 | @import '@uppy/core/src/_variables.scss'; 3 | 4 | .uppy-Audio-container { 5 | width: 100%; 6 | height: 100%; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | flex-direction: column; 11 | } 12 | 13 | .uppy-Audio-audioContainer { 14 | display: flex; 15 | width: 100%; 16 | height: 100%; 17 | background-color: $gray-300; 18 | position: relative; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | 23 | .uppy-Audio-player { 24 | width: 85%; 25 | border-radius: 12px; 26 | } 27 | 28 | .uppy-Audio-canvas { 29 | width: 100%; 30 | height: 100%; 31 | position: absolute; 32 | top: 0; 33 | left: 0; 34 | right: 0; 35 | bottom: 0; 36 | } 37 | 38 | .uppy-Audio-footer { 39 | width: 100%; 40 | // min-height: 75px; 41 | display: flex; 42 | flex-wrap: wrap; 43 | align-items: center; 44 | justify-content: space-between; 45 | padding: 20px 20px; 46 | } 47 | 48 | .uppy-Audio-audioSourceContainer { 49 | width: 100%; 50 | flex-grow: 0; 51 | } 52 | 53 | .uppy-size--lg .uppy-Audio-audioSourceContainer { 54 | width: 33%; 55 | margin: 0; // vertical alignment handled by the flexbox wrapper 56 | } 57 | 58 | .uppy-Audio-audioSource-select { 59 | display: block; 60 | font-size: 16px; 61 | line-height: 1.2; 62 | padding: 0.4em 1em 0.3em 0.4em; 63 | width: 100%; 64 | max-width: 90%; 65 | border: 1px solid $gray-600; 66 | background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23757575%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'); 67 | background-repeat: no-repeat; 68 | background-position: 69 | right 0.4em top 50%, 70 | 0 0; 71 | background-size: 72 | 0.65em auto, 73 | 100%; 74 | margin: auto; 75 | margin-bottom: 10px; 76 | white-space: nowrap; 77 | text-overflow: ellipsis; 78 | 79 | .uppy-size--lg & { 80 | font-size: 14px; 81 | margin-bottom: 0; 82 | } 83 | } 84 | 85 | .uppy-Audio-audioSource-select::-ms-expand { 86 | display: none; 87 | } 88 | 89 | .uppy-Audio-buttonContainer { 90 | width: 50%; 91 | margin-left: 25%; 92 | text-align: center; 93 | flex: 1; 94 | } 95 | 96 | .uppy-size--lg .uppy-Audio-buttonContainer { 97 | width: 34%; 98 | margin-left: 0; 99 | } 100 | 101 | .uppy-Audio-recordingLength { 102 | width: 25%; 103 | flex-grow: 0; 104 | color: $gray-600; 105 | font-family: $font-family-mono; 106 | text-align: right; 107 | } 108 | 109 | .uppy-size--lg .uppy-Audio-recordingLength { 110 | width: 33%; 111 | } 112 | 113 | .uppy-Audio-button { 114 | @include blue-border-focus; 115 | width: 45px; 116 | height: 45px; 117 | border-radius: 50%; 118 | background-color: $red; 119 | color: $white; 120 | cursor: pointer; 121 | transition: all 0.3s; 122 | 123 | &:hover { 124 | background-color: darken($red, 5%); 125 | } 126 | 127 | [data-uppy-theme='dark'] & { 128 | @include blue-border-focus--dark; 129 | } 130 | } 131 | 132 | .uppy-Audio-button--submit { 133 | background-color: $blue; 134 | margin: 0 12px; 135 | 136 | &:hover { 137 | background-color: darken($blue, 5%); 138 | } 139 | } 140 | 141 | .uppy-Audio-button svg { 142 | width: 26px; 143 | height: 26px; 144 | max-width: 100%; 145 | max-height: 100%; 146 | display: inline-block; 147 | vertical-align: text-top; 148 | overflow: hidden; 149 | fill: currentColor; 150 | } 151 | 152 | .uppy-size--md .uppy-Audio-button { 153 | width: 60px; 154 | height: 60px; 155 | } 156 | 157 | .uppy-Audio-permissons { 158 | padding: 15px; 159 | display: flex; 160 | align-items: center; 161 | justify-content: center; 162 | flex-flow: column wrap; 163 | height: 100%; 164 | flex: 1; 165 | } 166 | 167 | .uppy-Audio-permissons p { 168 | max-width: 450px; 169 | line-height: 1.3; 170 | text-align: center; 171 | line-height: 1.45; 172 | color: $gray-500; 173 | margin: 0; 174 | } 175 | 176 | .uppy-Audio-permissonsIcon svg { 177 | width: 100px; 178 | height: 75px; 179 | color: $gray-400; 180 | margin-bottom: 30px; 181 | } 182 | 183 | .uppy-Audio-title { 184 | font-size: 22px; 185 | line-height: 1.35; 186 | font-weight: 400; 187 | margin: 0; 188 | margin-bottom: 5px; 189 | padding: 0 15px; 190 | max-width: 500px; 191 | text-align: center; 192 | color: $gray-800; 193 | 194 | [data-uppy-theme='dark'] & { 195 | color: $gray-200; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { BasePlugin, PluginOptions, UppyFile } from '@uppy/core' 2 | 3 | type MaybePromise = T | Promise 4 | 5 | export type AwsS3UploadParameters = 6 | | { 7 | method: 'POST' 8 | url: string 9 | fields: Record 10 | expires?: number 11 | headers?: Record 12 | } 13 | | { 14 | method?: 'PUT' 15 | url: string 16 | fields?: Record 17 | expires?: number 18 | headers?: Record 19 | } 20 | 21 | export interface AwsS3Part { 22 | PartNumber?: number 23 | Size?: number 24 | ETag?: string 25 | } 26 | /** 27 | * @deprecated use {@link AwsS3UploadParameters} instead 28 | */ 29 | export interface AwsS3SignedPart { 30 | url: string 31 | headers?: Record 32 | } 33 | export interface AwsS3STSResponse { 34 | credentials: { 35 | AccessKeyId: string 36 | SecretAccessKey: string 37 | SessionToken: string 38 | Expiration?: string 39 | } 40 | bucket: string 41 | region: string 42 | } 43 | 44 | type AWSS3NonMultipartWithCompanionMandatory = { 45 | getUploadParameters?: never 46 | } 47 | 48 | type AWSS3NonMultipartWithoutCompanionMandatory = { 49 | getUploadParameters: (file: UppyFile) => MaybePromise 50 | } 51 | type AWSS3NonMultipartWithCompanion = AWSS3WithCompanion & 52 | AWSS3NonMultipartWithCompanionMandatory & { 53 | shouldUseMultipart: false 54 | createMultipartUpload?: never 55 | listParts?: never 56 | signPart?: never 57 | abortMultipartUpload?: never 58 | completeMultipartUpload?: never 59 | } 60 | 61 | type AWSS3NonMultipartWithoutCompanion = AWSS3WithoutCompanion & 62 | AWSS3NonMultipartWithoutCompanionMandatory & { 63 | shouldUseMultipart: false 64 | createMultipartUpload?: never 65 | listParts?: never 66 | signPart?: never 67 | abortMultipartUpload?: never 68 | completeMultipartUpload?: never 69 | } 70 | 71 | type AWSS3MultipartWithoutCompanionMandatory = { 72 | getChunkSize?: (file: UppyFile) => number 73 | createMultipartUpload: ( 74 | file: UppyFile, 75 | ) => MaybePromise<{ uploadId: string; key: string }> 76 | listParts: ( 77 | file: UppyFile, 78 | opts: { uploadId: string; key: string; signal: AbortSignal }, 79 | ) => MaybePromise 80 | abortMultipartUpload: ( 81 | file: UppyFile, 82 | opts: { uploadId: string; key: string; signal: AbortSignal }, 83 | ) => MaybePromise 84 | completeMultipartUpload: ( 85 | file: UppyFile, 86 | opts: { 87 | uploadId: string 88 | key: string 89 | parts: AwsS3Part[] 90 | signal: AbortSignal 91 | }, 92 | ) => MaybePromise<{ location?: string }> 93 | } & ( 94 | | { 95 | signPart: ( 96 | file: UppyFile, 97 | opts: { 98 | uploadId: string 99 | key: string 100 | partNumber: number 101 | body: Blob 102 | signal: AbortSignal 103 | }, 104 | ) => MaybePromise 105 | } 106 | | { 107 | /** @deprecated Use signPart instead */ 108 | prepareUploadParts: ( 109 | file: UppyFile, 110 | partData: { 111 | uploadId: string 112 | key: string 113 | parts: [{ number: number; chunk: Blob }] 114 | }, 115 | ) => MaybePromise<{ 116 | presignedUrls: Record 117 | headers?: Record> 118 | }> 119 | } 120 | ) 121 | type AWSS3MultipartWithoutCompanion = AWSS3WithoutCompanion & 122 | AWSS3MultipartWithoutCompanionMandatory & { 123 | shouldUseMultipart?: true 124 | getUploadParameters?: never 125 | } 126 | 127 | type AWSS3MultipartWithCompanion = AWSS3WithCompanion & 128 | Partial & { 129 | shouldUseMultipart?: true 130 | getUploadParameters?: never 131 | } 132 | 133 | type AWSS3MaybeMultipartWithCompanion = AWSS3WithCompanion & 134 | Partial & 135 | AWSS3NonMultipartWithCompanionMandatory & { 136 | shouldUseMultipart: (file: UppyFile) => boolean 137 | } 138 | 139 | type AWSS3MaybeMultipartWithoutCompanion = AWSS3WithoutCompanion & 140 | AWSS3MultipartWithoutCompanionMandatory & 141 | AWSS3NonMultipartWithoutCompanionMandatory & { 142 | shouldUseMultipart: (file: UppyFile) => boolean 143 | } 144 | 145 | type AWSS3WithCompanion = { 146 | companionUrl: string 147 | companionHeaders?: Record 148 | companionCookiesRule?: string 149 | getTemporarySecurityCredentials?: true 150 | } 151 | type AWSS3WithoutCompanion = { 152 | companionUrl?: never 153 | companionHeaders?: never 154 | companionCookiesRule?: never 155 | getTemporarySecurityCredentials?: (options?: { 156 | signal?: AbortSignal 157 | }) => MaybePromise 158 | } 159 | 160 | interface _AwsS3MultipartOptions extends PluginOptions { 161 | allowedMetaFields?: string[] | null 162 | limit?: number 163 | retryDelays?: number[] | null 164 | } 165 | 166 | export type AwsS3MultipartOptions = _AwsS3MultipartOptions & 167 | ( 168 | | AWSS3NonMultipartWithCompanion 169 | | AWSS3NonMultipartWithoutCompanion 170 | | AWSS3MultipartWithCompanion 171 | | AWSS3MultipartWithoutCompanion 172 | | AWSS3MaybeMultipartWithCompanion 173 | | AWSS3MaybeMultipartWithoutCompanion 174 | ) 175 | 176 | declare class AwsS3Multipart extends BasePlugin {} 177 | 178 | export default AwsS3Multipart 179 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/Socket.test.js: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach, vi, describe, it, expect } from 'vitest' 2 | import UppySocket from './Socket.js' 3 | 4 | describe('Socket', () => { 5 | let webSocketConstructorSpy 6 | let webSocketCloseSpy 7 | let webSocketSendSpy 8 | 9 | beforeEach(() => { 10 | webSocketConstructorSpy = vi.fn() 11 | webSocketCloseSpy = vi.fn() 12 | webSocketSendSpy = vi.fn() 13 | 14 | globalThis.WebSocket = class WebSocket { 15 | constructor (target) { 16 | webSocketConstructorSpy(target) 17 | } 18 | 19 | // eslint-disable-next-line class-methods-use-this 20 | close (args) { 21 | webSocketCloseSpy(args) 22 | } 23 | 24 | // eslint-disable-next-line class-methods-use-this 25 | send (json) { 26 | webSocketSendSpy(json) 27 | } 28 | 29 | triggerOpen () { 30 | this.onopen() 31 | } 32 | 33 | triggerClose () { 34 | this.onclose() 35 | } 36 | } 37 | }) 38 | afterEach(() => { 39 | globalThis.WebSocket = undefined 40 | }) 41 | 42 | it('should expose a class', () => { 43 | expect(UppySocket.name).toEqual('UppySocket') 44 | expect( 45 | new UppySocket({ 46 | target: 'foo', 47 | }) instanceof UppySocket, 48 | ) 49 | }) 50 | 51 | it('should setup a new WebSocket', () => { 52 | new UppySocket({ target: 'foo' }) // eslint-disable-line no-new 53 | expect(webSocketConstructorSpy.mock.calls[0][0]).toEqual('foo') 54 | }) 55 | 56 | it('should send a message via the websocket if the connection is open', () => { 57 | const uppySocket = new UppySocket({ target: 'foo' }) 58 | const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() 59 | webSocketInstance.triggerOpen() 60 | 61 | uppySocket.send('bar', 'boo') 62 | expect(webSocketSendSpy.mock.calls.length).toEqual(1) 63 | expect(webSocketSendSpy.mock.calls[0]).toEqual([ 64 | JSON.stringify({ action: 'bar', payload: 'boo' }), 65 | ]) 66 | }) 67 | 68 | it('should queue the message for the websocket if the connection is not open', () => { 69 | const uppySocket = new UppySocket({ target: 'foo' }) 70 | 71 | uppySocket.send('bar', 'boo') 72 | expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([{ action: 'bar', payload: 'boo' }]) 73 | expect(webSocketSendSpy.mock.calls.length).toEqual(0) 74 | }) 75 | 76 | it('should queue any messages for the websocket if the connection is not open, then send them when the connection is open', () => { 77 | const uppySocket = new UppySocket({ target: 'foo' }) 78 | const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() 79 | 80 | uppySocket.send('bar', 'boo') 81 | uppySocket.send('moo', 'baa') 82 | expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([ 83 | { action: 'bar', payload: 'boo' }, 84 | { action: 'moo', payload: 'baa' }, 85 | ]) 86 | expect(webSocketSendSpy.mock.calls.length).toEqual(0) 87 | 88 | webSocketInstance.triggerOpen() 89 | 90 | expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([]) 91 | expect(webSocketSendSpy.mock.calls.length).toEqual(2) 92 | expect(webSocketSendSpy.mock.calls[0]).toEqual([ 93 | JSON.stringify({ action: 'bar', payload: 'boo' }), 94 | ]) 95 | expect(webSocketSendSpy.mock.calls[1]).toEqual([ 96 | JSON.stringify({ action: 'moo', payload: 'baa' }), 97 | ]) 98 | }) 99 | 100 | it('should start queuing any messages when the websocket connection is closed', () => { 101 | const uppySocket = new UppySocket({ target: 'foo' }) 102 | const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() 103 | webSocketInstance.triggerOpen() 104 | uppySocket.send('bar', 'boo') 105 | expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([]) 106 | 107 | webSocketInstance.triggerClose() 108 | uppySocket.send('bar', 'boo') 109 | expect(uppySocket[Symbol.for('uppy test: getQueued')]()).toEqual([{ action: 'bar', payload: 'boo' }]) 110 | }) 111 | 112 | it('should close the websocket when it is force closed', () => { 113 | const uppySocket = new UppySocket({ target: 'foo' }) 114 | const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() 115 | webSocketInstance.triggerOpen() 116 | 117 | uppySocket.close() 118 | expect(webSocketCloseSpy.mock.calls.length).toEqual(1) 119 | }) 120 | 121 | it('should be able to subscribe to messages received on the websocket', () => { 122 | const uppySocket = new UppySocket({ target: 'foo' }) 123 | const webSocketInstance = uppySocket[Symbol.for('uppy test: getSocket')]() 124 | 125 | const emitterListenerMock = vi.fn() 126 | uppySocket.on('hi', emitterListenerMock) 127 | 128 | webSocketInstance.triggerOpen() 129 | webSocketInstance.onmessage({ 130 | data: JSON.stringify({ action: 'hi', payload: 'ho' }), 131 | }) 132 | expect(emitterListenerMock.mock.calls).toEqual([ 133 | ['ho', undefined, undefined, undefined, undefined, undefined], 134 | ]) 135 | }) 136 | 137 | it('should be able to emit messages and subscribe to them', () => { 138 | const uppySocket = new UppySocket({ target: 'foo' }) 139 | 140 | const emitterListenerMock = vi.fn() 141 | uppySocket.on('hi', emitterListenerMock) 142 | 143 | uppySocket.emit('hi', 'ho') 144 | uppySocket.emit('hi', 'ho') 145 | uppySocket.emit('hi', 'off to work we go') 146 | 147 | expect(emitterListenerMock.mock.calls).toEqual([ 148 | ['ho', undefined, undefined, undefined, undefined, undefined], 149 | ['ho', undefined, undefined, undefined, undefined, undefined], 150 | [ 151 | 'off to work we go', 152 | undefined, 153 | undefined, 154 | undefined, 155 | undefined, 156 | undefined, 157 | ], 158 | ]) 159 | }) 160 | 161 | it('should be able to subscribe to the first event for a particular action', () => { 162 | const uppySocket = new UppySocket({ target: 'foo' }) 163 | 164 | const emitterListenerMock = vi.fn() 165 | uppySocket.once('hi', emitterListenerMock) 166 | 167 | uppySocket.emit('hi', 'ho') 168 | uppySocket.emit('hi', 'ho') 169 | uppySocket.emit('hi', 'off to work we go') 170 | 171 | expect(emitterListenerMock.mock.calls.length).toEqual(1) 172 | expect(emitterListenerMock.mock.calls).toEqual([ 173 | ['ho', undefined, undefined, undefined, undefined, undefined], 174 | ]) 175 | }) 176 | }) 177 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/src/createSignedURL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a canonical request by concatenating the following strings, separated 3 | * by newline characters. This helps ensure that the signature that you 4 | * calculate and the signature that AWS calculates can match. 5 | * 6 | * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-canonical-request 7 | * 8 | * @param {object} param0 9 | * @param {string} param0.method – The HTTP method. 10 | * @param {string} param0.CanonicalUri – The URI-encoded version of the absolute 11 | * path component URL (everything between the host and the question mark 12 | * character (?) that starts the query string parameters). If the absolute path 13 | * is empty, use a forward slash character (/). 14 | * @param {string} param0.CanonicalQueryString – The URL-encoded query string 15 | * parameters, separated by ampersands (&). Percent-encode reserved characters, 16 | * including the space character. Encode names and values separately. If there 17 | * are empty parameters, append the equals sign to the parameter name before 18 | * encoding. After encoding, sort the parameters alphabetically by key name. If 19 | * there is no query string, use an empty string (""). 20 | * @param {Record} param0.SignedHeaders – The request headers, 21 | * that will be signed, and their values, separated by newline characters. 22 | * For the values, trim any leading or trailing spaces, convert sequential 23 | * spaces to a single space, and separate the values for a multi-value header 24 | * using commas. You must include the host header (HTTP/1.1), and any x-amz-* 25 | * headers in the signature. You can optionally include other standard headers 26 | * in the signature, such as content-type. 27 | * @param {string} param0.HashedPayload – A string created using the payload in 28 | * the body of the HTTP request as input to a hash function. This string uses 29 | * lowercase hexadecimal characters. If the payload is empty, use an empty 30 | * string as the input to the hash function. 31 | * @returns {string} 32 | */ 33 | function createCanonicalRequest ({ 34 | method = 'PUT', 35 | CanonicalUri = '/', 36 | CanonicalQueryString = '', 37 | SignedHeaders, 38 | HashedPayload, 39 | }) { 40 | const headerKeys = Object.keys(SignedHeaders).map(k => k.toLowerCase()).sort() 41 | return [ 42 | method, 43 | CanonicalUri, 44 | CanonicalQueryString, 45 | ...headerKeys.map(k => `${k}:${SignedHeaders[k]}`), 46 | '', 47 | headerKeys.join(';'), 48 | HashedPayload, 49 | ].join('\n') 50 | } 51 | 52 | const ec = new TextEncoder() 53 | const algorithm = { name: 'HMAC', hash: 'SHA-256' } 54 | 55 | async function digest (data) { 56 | const { subtle } = globalThis.crypto 57 | return subtle.digest(algorithm.hash, ec.encode(data)) 58 | } 59 | 60 | async function generateHmacKey (secret) { 61 | const { subtle } = globalThis.crypto 62 | return subtle.importKey('raw', typeof secret === 'string' ? ec.encode(secret) : secret, algorithm, false, ['sign']) 63 | } 64 | 65 | function arrayBufferToHexString (arrayBuffer) { 66 | const byteArray = new Uint8Array(arrayBuffer) 67 | let hexString = '' 68 | for (let i = 0; i < byteArray.length; i++) { 69 | hexString += byteArray[i].toString(16).padStart(2, '0') 70 | } 71 | return hexString 72 | } 73 | 74 | async function hash (key, data) { 75 | const { subtle } = globalThis.crypto 76 | return subtle.sign(algorithm, await generateHmacKey(key), ec.encode(data)) 77 | } 78 | 79 | /** 80 | * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html 81 | * @param {Record} param0 82 | * @returns {Promise} the signed URL 83 | */ 84 | export default async function createSignedURL ({ 85 | accountKey, accountSecret, sessionToken, 86 | bucketName, 87 | Key, Region, 88 | expires, 89 | uploadId, partNumber, 90 | }) { 91 | const Service = 's3' 92 | const host = `${bucketName}.${Service}.${Region}.amazonaws.com` 93 | const CanonicalUri = `/${encodeURI(Key)}` 94 | const payload = 'UNSIGNED-PAYLOAD' 95 | 96 | const requestDateTime = new Date().toISOString().replace(/[-:]|\.\d+/g, '') // YYYYMMDDTHHMMSSZ 97 | const date = requestDateTime.slice(0, 8) // YYYYMMDD 98 | const scope = `${date}/${Region}/${Service}/aws4_request` 99 | 100 | const url = new URL(`https://${host}${CanonicalUri}`) 101 | // N.B.: URL search params needs to be added in the ASCII order 102 | url.searchParams.set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256') 103 | url.searchParams.set('X-Amz-Content-Sha256', payload) 104 | url.searchParams.set('X-Amz-Credential', `${accountKey}/${scope}`) 105 | url.searchParams.set('X-Amz-Date', requestDateTime) 106 | url.searchParams.set('X-Amz-Expires', expires) 107 | // We are signing on the client, so we expect there's going to be a session token: 108 | url.searchParams.set('X-Amz-Security-Token', sessionToken) 109 | url.searchParams.set('X-Amz-SignedHeaders', 'host') 110 | // Those two are present only for Multipart Uploads: 111 | if (partNumber) url.searchParams.set('partNumber', partNumber) 112 | if (uploadId) url.searchParams.set('uploadId', uploadId) 113 | url.searchParams.set('x-id', partNumber && uploadId ? 'UploadPart' : 'PutObject') 114 | 115 | // Step 1: Create a canonical request 116 | const canonical = createCanonicalRequest({ 117 | CanonicalUri, 118 | CanonicalQueryString: url.search.slice(1), 119 | SignedHeaders: { 120 | host, 121 | }, 122 | HashedPayload: payload, 123 | }) 124 | 125 | // Step 2: Create a hash of the canonical request 126 | const hashedCanonical = arrayBufferToHexString(await digest(canonical)) 127 | 128 | // Step 3: Create a string to sign 129 | const stringToSign = [ 130 | `AWS4-HMAC-SHA256`, // The algorithm used to create the hash of the canonical request. 131 | requestDateTime, // The date and time used in the credential scope. 132 | scope, // The credential scope. This restricts the resulting signature to the specified Region and service. 133 | hashedCanonical, // The hash of the canonical request. 134 | ].join('\n') 135 | 136 | // Step 4: Calculate the signature 137 | const kDate = await hash(`AWS4${accountSecret}`, date) 138 | const kRegion = await hash(kDate, Region) 139 | const kService = await hash(kRegion, Service) 140 | const kSigning = await hash(kService, 'aws4_request') 141 | const signature = arrayBufferToHexString(await hash(kSigning, stringToSign)) 142 | 143 | // Step 5: Add the signature to the request 144 | url.searchParams.set('X-Amz-Signature', signature) 145 | 146 | return url 147 | } 148 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @uppy/aws-s3 2 | 3 | ## 3.3.0 4 | 5 | Released: 2023-09-05 6 | Included in: Uppy v3.15.0 7 | 8 | - @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) 9 | 10 | ## 3.2.3 11 | 12 | Released: 2023-08-23 13 | Included in: Uppy v3.14.1 14 | 15 | - @uppy/aws-s3-multipart,@uppy/aws-s3: allow empty objects for `fields` types (Antoine du Hamel / #4631) 16 | 17 | ## 3.2.2 18 | 19 | Released: 2023-08-15 20 | Included in: Uppy v3.14.0 21 | 22 | - @uppy/aws-s3,@uppy/aws-s3-multipart: update types (Antoine du Hamel / #4611) 23 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/companion,@uppy/transloadit,@uppy/xhr-upload: use uppercase HTTP method names (Antoine du Hamel / #4612) 24 | - @uppy/aws-s3,@uppy/aws-s3-multipart: update types (bdirito / #4576) 25 | - @uppy/aws-s3,@uppy/tus,@uppy/xhr-upload: Invoke headers function for remote uploads (Dominik Schmidt / #4596) 26 | 27 | ## 3.2.1 28 | 29 | Released: 2023-07-06 30 | Included in: Uppy v3.11.0 31 | 32 | - @uppy/aws-s3: fix remote uploads (Antoine du Hamel / #4546) 33 | 34 | ## 3.2.0 35 | 36 | Released: 2023-06-19 37 | Included in: Uppy v3.10.0 38 | 39 | - @uppy/aws-s3: add `shouldUseMultipart` option (Antoine du Hamel / #4299) 40 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/tus,@uppy/utils,@uppy/xhr-upload: When file is removed (or all are canceled), controller.abort queued requests (Artur Paikin / #4504) 41 | 42 | ## 3.1.1 43 | 44 | Released: 2023-05-02 45 | Included in: Uppy v3.9.0 46 | 47 | - @uppy/aws-s3: deprecate `timeout` option (Antoine du Hamel / #4298) 48 | 49 | ## 3.0.6 50 | 51 | Released: 2023-04-04 52 | Included in: Uppy v3.7.0 53 | 54 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/tus,@uppy/xhr-upload: make sure that we reset serverToken when an upload fails (Mikael Finstad / #4376) 55 | - @uppy/aws-s3: Update types (Minh Hieu / #4294) 56 | 57 | ## 3.0.5 58 | 59 | Released: 2023-01-26 60 | Included in: Uppy v3.4.0 61 | 62 | - @uppy/aws-s3: fix: add https:// to digital oceans link (Le Gia Hoang / #4165) 63 | 64 | ## 3.0.4 65 | 66 | Released: 2022-10-24 67 | Included in: Uppy v3.2.2 68 | 69 | - @uppy/aws-s3,@uppy/tus,@uppy/xhr-upload: replace `this.getState().files` with `this.uppy.getState().files` (Artur Paikin / #4167) 70 | 71 | ## 3.0.3 72 | 73 | Released: 2022-10-19 74 | Included in: Uppy v3.2.0 75 | 76 | - @uppy/aws-s3,@uppy/xhr-upload: fix `Cannot mark a queued request as done` in `MiniXHRUpload` (Antoine du Hamel / #4151) 77 | 78 | ## 3.0.2 79 | 80 | Released: 2022-09-25 81 | Included in: Uppy v3.1.0 82 | 83 | - @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) 84 | 85 | ## 3.0.0 86 | 87 | Released: 2022-08-22 88 | Included in: Uppy v3.0.0 89 | 90 | - @uppy/aws-s3,@uppy/tus,@uppy/xhr-upload: @uppy/tus, @uppy/xhr-upload, @uppy/aws-s3: `metaFields` -> `allowedMetaFields` (Merlijn Vos / #4023) 91 | - @uppy/aws-s3: aws-s3: fix incorrect comparison for `file-removed` (Merlijn Vos / #3962) 92 | - Switch to ESM 93 | 94 | ## 3.0.0-beta.3 95 | 96 | Released: 2022-08-16 97 | Included in: Uppy v3.0.0-beta.5 98 | 99 | - @uppy/aws-s3: Export AwsS3UploadParameters & AwsS3Options interfaces (Antonina Vertsinskaya / #3956) 100 | 101 | ## 3.0.0-beta.2 102 | 103 | Released: 2022-07-27 104 | Included in: Uppy v3.0.0-beta.3 105 | 106 | - @uppy/aws-s3,@uppy/core,@uppy/dashboard,@uppy/store-redux,@uppy/xhr-upload: upgrade `nanoid` to v4 (Antoine du Hamel / #3904) 107 | 108 | ## 2.2.1 109 | 110 | Released: 2022-06-07 111 | Included in: Uppy v2.12.0 112 | 113 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/tus: queue socket token requests for remote files (Merlijn Vos / #3797) 114 | 115 | ## 2.2.0 116 | 117 | Released: 2022-05-30 118 | Included in: Uppy v2.11.0 119 | 120 | - @uppy/angular,@uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/onedrive,@uppy/progress-bar,@uppy/react,@uppy/redux-dev-tools,@uppy/robodog,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: doc: update bundler recommendation (Antoine du Hamel / #3763) 121 | - @uppy/aws-s3: fix JSDoc type error (Antoine du Hamel / #3785) 122 | - @uppy/aws-s3: refactor to ESM (Antoine du Hamel / #3673) 123 | 124 | ## 2.1.0 125 | 126 | Released: 2022-05-14 127 | Included in: Uppy v2.10.0 128 | 129 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/core,@uppy/react,@uppy/transloadit,@uppy/tus,@uppy/xhr-upload: proposal: Cancel assemblies optional (Mikael Finstad / #3575) 130 | 131 | ## 2.0.9 132 | 133 | Released: 2022-04-07 134 | Included in: Uppy v2.9.2 135 | 136 | - @uppy/aws-s3,@uppy/companion-client,@uppy/transloadit,@uppy/utils: Propagate `isNetworkError` through error wrappers (Renée Kooi / #3620) 137 | 138 | ## 2.0.8 139 | 140 | Released: 2022-03-16 141 | Included in: Uppy v2.8.0 142 | 143 | - @uppy/aws-s3: fix wrong events being sent to companion (Mikael Finstad / #3576) 144 | 145 | ## 2.0.7 146 | 147 | Released: 2021-12-09 148 | Included in: Uppy v2.3.1 149 | 150 | - @uppy/aws-s3,@uppy/core,@uppy/dashboard,@uppy/store-redux,@uppy/xhr-upload: deps: use `nanoid/non-secure` to workaround react-native limitation (Antoine du Hamel / #3350) 151 | 152 | ## 2.0.6 153 | 154 | Released: 2021-12-07 155 | Included in: Uppy v2.3.0 156 | 157 | - @uppy/aws-s3,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/google-drive,@uppy/image-editor,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/screen-capture,@uppy/status-bar,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/url,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: Refactor locale scripts & generate types and docs (Merlijn Vos / #3276) 158 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/src/MultipartUploader.js: -------------------------------------------------------------------------------- 1 | import { AbortController } from '@uppy/utils/lib/AbortController' 2 | 3 | const MB = 1024 * 1024 4 | 5 | const defaultOptions = { 6 | getChunkSize (file) { 7 | return Math.ceil(file.size / 10000) 8 | }, 9 | onProgress () {}, 10 | onPartComplete () {}, 11 | onSuccess () {}, 12 | onError (err) { 13 | throw err 14 | }, 15 | } 16 | 17 | function ensureInt (value) { 18 | if (typeof value === 'string') { 19 | return parseInt(value, 10) 20 | } 21 | if (typeof value === 'number') { 22 | return value 23 | } 24 | throw new TypeError('Expected a number') 25 | } 26 | 27 | export const pausingUploadReason = Symbol('pausing upload, not an actual error') 28 | 29 | /** 30 | * A MultipartUploader instance is used per file upload to determine whether a 31 | * upload should be done as multipart or as a regular S3 upload 32 | * (based on the user-provided `shouldUseMultipart` option value) and to manage 33 | * the chunk splitting. 34 | */ 35 | class MultipartUploader { 36 | #abortController = new AbortController() 37 | 38 | /** @type {import("../types/chunk").Chunk[]} */ 39 | #chunks 40 | 41 | /** @type {{ uploaded: number, etag?: string, done?: boolean }[]} */ 42 | #chunkState 43 | 44 | /** 45 | * The (un-chunked) data to upload. 46 | * 47 | * @type {Blob} 48 | */ 49 | #data 50 | 51 | /** @type {import("@uppy/core").UppyFile} */ 52 | #file 53 | 54 | /** @type {boolean} */ 55 | #uploadHasStarted = false 56 | 57 | /** @type {(err?: Error | any) => void} */ 58 | #onError 59 | 60 | /** @type {() => void} */ 61 | #onSuccess 62 | 63 | /** @type {import('../types/index').AwsS3MultipartOptions["shouldUseMultipart"]} */ 64 | #shouldUseMultipart 65 | 66 | /** @type {boolean} */ 67 | #isRestoring 68 | 69 | #onReject = (err) => (err?.cause === pausingUploadReason ? null : this.#onError(err)) 70 | 71 | #maxMultipartParts = 10_000 72 | 73 | #minPartSize = 5 * MB 74 | 75 | constructor (data, options) { 76 | this.options = { 77 | ...defaultOptions, 78 | ...options, 79 | } 80 | // Use default `getChunkSize` if it was null or something 81 | this.options.getChunkSize ??= defaultOptions.getChunkSize 82 | 83 | this.#data = data 84 | this.#file = options.file 85 | this.#onSuccess = this.options.onSuccess 86 | this.#onError = this.options.onError 87 | this.#shouldUseMultipart = this.options.shouldUseMultipart 88 | 89 | // When we are restoring an upload, we already have an UploadId and a Key. Otherwise 90 | // we need to call `createMultipartUpload` to get an `uploadId` and a `key`. 91 | // Non-multipart uploads are not restorable. 92 | this.#isRestoring = options.uploadId && options.key 93 | 94 | this.#initChunks() 95 | } 96 | 97 | // initChunks checks the user preference for using multipart uploads (opts.shouldUseMultipart) 98 | // and calculates the optimal part size. When using multipart part uploads every part except for the last has 99 | // to be at least 5 MB and there can be no more than 10K parts. 100 | // This means we sometimes need to change the preferred part size from the user in order to meet these requirements. 101 | #initChunks () { 102 | const fileSize = this.#data.size 103 | const shouldUseMultipart = typeof this.#shouldUseMultipart === 'function' 104 | ? this.#shouldUseMultipart(this.#file) 105 | : Boolean(this.#shouldUseMultipart) 106 | 107 | if (shouldUseMultipart && fileSize > this.#minPartSize) { 108 | // At least 5MB per request: 109 | let chunkSize = Math.max(this.options.getChunkSize(this.#data), this.#minPartSize) 110 | let arraySize = Math.floor(fileSize / chunkSize) 111 | 112 | // At most 10k requests per file: 113 | if (arraySize > this.#maxMultipartParts) { 114 | arraySize = this.#maxMultipartParts 115 | chunkSize = fileSize / this.#maxMultipartParts 116 | } 117 | this.#chunks = Array(arraySize) 118 | 119 | for (let offset = 0, j = 0; offset < fileSize; offset += chunkSize, j++) { 120 | const end = Math.min(fileSize, offset + chunkSize) 121 | 122 | // Defer data fetching/slicing until we actually need the data, because it's slow if we have a lot of files 123 | const getData = () => { 124 | const i2 = offset 125 | return this.#data.slice(i2, end) 126 | } 127 | 128 | this.#chunks[j] = { 129 | getData, 130 | onProgress: this.#onPartProgress(j), 131 | onComplete: this.#onPartComplete(j), 132 | shouldUseMultipart, 133 | } 134 | if (this.#isRestoring) { 135 | const size = offset + chunkSize > fileSize ? fileSize - offset : chunkSize 136 | // setAsUploaded is called by listPart, to keep up-to-date the 137 | // quantity of data that is left to actually upload. 138 | this.#chunks[j].setAsUploaded = () => { 139 | this.#chunks[j] = null 140 | this.#chunkState[j].uploaded = size 141 | } 142 | } 143 | } 144 | } else { 145 | this.#chunks = [{ 146 | getData: () => this.#data, 147 | onProgress: this.#onPartProgress(0), 148 | onComplete: this.#onPartComplete(0), 149 | shouldUseMultipart, 150 | }] 151 | } 152 | 153 | this.#chunkState = this.#chunks.map(() => ({ uploaded: 0 })) 154 | } 155 | 156 | #createUpload () { 157 | this 158 | .options.companionComm.uploadFile(this.#file, this.#chunks, this.#abortController.signal) 159 | .then(this.#onSuccess, this.#onReject) 160 | this.#uploadHasStarted = true 161 | } 162 | 163 | #resumeUpload () { 164 | this 165 | .options.companionComm.resumeUploadFile(this.#file, this.#chunks, this.#abortController.signal) 166 | .then(this.#onSuccess, this.#onReject) 167 | } 168 | 169 | #onPartProgress = (index) => (ev) => { 170 | if (!ev.lengthComputable) return 171 | 172 | this.#chunkState[index].uploaded = ensureInt(ev.loaded) 173 | 174 | const totalUploaded = this.#chunkState.reduce((n, c) => n + c.uploaded, 0) 175 | this.options.onProgress(totalUploaded, this.#data.size) 176 | } 177 | 178 | #onPartComplete = (index) => (etag) => { 179 | // This avoids the net::ERR_OUT_OF_MEMORY in Chromium Browsers. 180 | this.#chunks[index] = null 181 | this.#chunkState[index].etag = etag 182 | this.#chunkState[index].done = true 183 | 184 | const part = { 185 | PartNumber: index + 1, 186 | ETag: etag, 187 | } 188 | this.options.onPartComplete(part) 189 | } 190 | 191 | #abortUpload () { 192 | this.#abortController.abort() 193 | this.options.companionComm.abortFileUpload(this.#file).catch((err) => this.options.log(err)) 194 | } 195 | 196 | start () { 197 | if (this.#uploadHasStarted) { 198 | if (!this.#abortController.signal.aborted) this.#abortController.abort(pausingUploadReason) 199 | this.#abortController = new AbortController() 200 | this.#resumeUpload() 201 | } else if (this.#isRestoring) { 202 | this.options.companionComm.restoreUploadFile(this.#file, { uploadId: this.options.uploadId, key: this.options.key }) 203 | this.#resumeUpload() 204 | } else { 205 | this.#createUpload() 206 | } 207 | } 208 | 209 | pause () { 210 | this.#abortController.abort(pausingUploadReason) 211 | // Swap it out for a new controller, because this instance may be resumed later. 212 | this.#abortController = new AbortController() 213 | } 214 | 215 | abort (opts = undefined) { 216 | if (opts?.really) this.#abortUpload() 217 | else this.pause() 218 | } 219 | 220 | // TODO: remove this in the next major 221 | get chunkState () { 222 | return this.#chunkState 223 | } 224 | } 225 | 226 | export default MultipartUploader 227 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/src/MiniXHRUpload.js: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid/non-secure' 2 | import EventManager from '@uppy/utils/lib/EventManager' 3 | import ProgressTimeout from '@uppy/utils/lib/ProgressTimeout' 4 | import ErrorWithCause from '@uppy/utils/lib/ErrorWithCause' 5 | import NetworkError from '@uppy/utils/lib/NetworkError' 6 | import isNetworkError from '@uppy/utils/lib/isNetworkError' 7 | import { internalRateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue' 8 | 9 | // See XHRUpload 10 | function buildResponseError (xhr, error) { 11 | if (isNetworkError(xhr)) return new NetworkError(error, xhr) 12 | 13 | const err = new ErrorWithCause('Upload error', { cause: error }) 14 | err.request = xhr 15 | return err 16 | } 17 | 18 | // See XHRUpload 19 | function setTypeInBlob (file) { 20 | const dataWithUpdatedType = file.data.slice(0, file.data.size, file.meta.type) 21 | return dataWithUpdatedType 22 | } 23 | 24 | function addMetadata (formData, meta, opts) { 25 | const allowedMetaFields = Array.isArray(opts.allowedMetaFields) 26 | ? opts.allowedMetaFields 27 | // Send along all fields by default. 28 | : Object.keys(meta) 29 | allowedMetaFields.forEach((item) => { 30 | formData.append(item, meta[item]) 31 | }) 32 | } 33 | 34 | function createFormDataUpload (file, opts) { 35 | const formPost = new FormData() 36 | 37 | addMetadata(formPost, file.meta, opts) 38 | 39 | const dataWithUpdatedType = setTypeInBlob(file) 40 | 41 | if (file.name) { 42 | formPost.append(opts.fieldName, dataWithUpdatedType, file.meta.name) 43 | } else { 44 | formPost.append(opts.fieldName, dataWithUpdatedType) 45 | } 46 | 47 | return formPost 48 | } 49 | 50 | const createBareUpload = file => file.data 51 | 52 | export default class MiniXHRUpload { 53 | constructor (uppy, opts) { 54 | this.uppy = uppy 55 | this.opts = { 56 | validateStatus (status) { 57 | return status >= 200 && status < 300 58 | }, 59 | ...opts, 60 | } 61 | 62 | this.requests = opts[internalRateLimitedQueue] 63 | this.uploaderEvents = Object.create(null) 64 | this.i18n = opts.i18n 65 | } 66 | 67 | getOptions (file) { 68 | const { uppy } = this 69 | 70 | const overrides = uppy.getState().xhrUpload 71 | const opts = { 72 | ...this.opts, 73 | ...(overrides || {}), 74 | ...(file.xhrUpload || {}), 75 | headers: { 76 | ...this.opts.headers, 77 | ...overrides?.headers, 78 | ...file.xhrUpload?.headers, 79 | }, 80 | } 81 | 82 | return opts 83 | } 84 | 85 | #addEventHandlerForFile (eventName, fileID, eventHandler) { 86 | this.uploaderEvents[fileID].on(eventName, (fileOrID) => { 87 | // TODO (major): refactor Uppy events to consistently send file objects (or consistently IDs) 88 | // We created a generic `addEventListenerForFile` but not all events 89 | // use file IDs, some use files, so we need to do this weird check. 90 | const id = fileOrID?.id ?? fileOrID 91 | if (fileID === id) eventHandler() 92 | }) 93 | } 94 | 95 | #addEventHandlerIfFileStillExists (eventName, fileID, eventHandler) { 96 | this.uploaderEvents[fileID].on(eventName, (...args) => { 97 | if (this.uppy.getFile(fileID)) eventHandler(...args) 98 | }) 99 | } 100 | 101 | uploadLocalFile (file) { 102 | const opts = this.getOptions(file) 103 | 104 | return new Promise((resolve, reject) => { 105 | // This is done in index.js in the S3 plugin. 106 | // this.uppy.emit('upload-started', file) 107 | 108 | const data = opts.formData 109 | ? createFormDataUpload(file, opts) 110 | : createBareUpload(file, opts) 111 | 112 | const xhr = new XMLHttpRequest() 113 | this.uploaderEvents[file.id] = new EventManager(this.uppy) 114 | 115 | const timer = new ProgressTimeout(opts.timeout, () => { 116 | xhr.abort() 117 | // eslint-disable-next-line no-use-before-define 118 | queuedRequest.done() 119 | const error = new Error(this.i18n('timedOut', { seconds: Math.ceil(opts.timeout / 1000) })) 120 | this.uppy.emit('upload-error', file, error) 121 | reject(error) 122 | }) 123 | 124 | const id = nanoid() 125 | 126 | xhr.upload.addEventListener('loadstart', () => { 127 | this.uppy.log(`[AwsS3/XHRUpload] ${id} started`) 128 | }) 129 | 130 | xhr.upload.addEventListener('progress', (ev) => { 131 | this.uppy.log(`[AwsS3/XHRUpload] ${id} progress: ${ev.loaded} / ${ev.total}`) 132 | // Begin checking for timeouts when progress starts, instead of loading, 133 | // to avoid timing out requests on browser concurrency queue 134 | timer.progress() 135 | 136 | if (ev.lengthComputable) { 137 | this.uppy.emit('upload-progress', file, { 138 | uploader: this, 139 | bytesUploaded: ev.loaded, 140 | bytesTotal: ev.total, 141 | }) 142 | } 143 | }) 144 | 145 | xhr.addEventListener('load', (ev) => { 146 | this.uppy.log(`[AwsS3/XHRUpload] ${id} finished`) 147 | timer.done() 148 | // eslint-disable-next-line no-use-before-define 149 | queuedRequest.done() 150 | if (this.uploaderEvents[file.id]) { 151 | this.uploaderEvents[file.id].remove() 152 | this.uploaderEvents[file.id] = null 153 | } 154 | 155 | if (opts.validateStatus(ev.target.status, xhr.responseText, xhr)) { 156 | const body = opts.getResponseData(xhr.responseText, xhr) 157 | const uploadURL = body[opts.responseUrlFieldName] 158 | 159 | const uploadResp = { 160 | status: ev.target.status, 161 | body, 162 | uploadURL, 163 | } 164 | 165 | this.uppy.emit('upload-success', file, uploadResp) 166 | 167 | if (uploadURL) { 168 | this.uppy.log(`Download ${file.name} from ${uploadURL}`) 169 | } 170 | 171 | return resolve(file) 172 | } 173 | const body = opts.getResponseData(xhr.responseText, xhr) 174 | const error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr)) 175 | 176 | const response = { 177 | status: ev.target.status, 178 | body, 179 | } 180 | 181 | this.uppy.emit('upload-error', file, error, response) 182 | return reject(error) 183 | }) 184 | 185 | xhr.addEventListener('error', () => { 186 | this.uppy.log(`[AwsS3/XHRUpload] ${id} errored`) 187 | timer.done() 188 | // eslint-disable-next-line no-use-before-define 189 | queuedRequest.done() 190 | if (this.uploaderEvents[file.id]) { 191 | this.uploaderEvents[file.id].remove() 192 | this.uploaderEvents[file.id] = null 193 | } 194 | 195 | const error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr)) 196 | this.uppy.emit('upload-error', file, error) 197 | return reject(error) 198 | }) 199 | 200 | xhr.open(opts.method.toUpperCase(), opts.endpoint, true) 201 | // IE10 does not allow setting `withCredentials` and `responseType` 202 | // before `open()` is called. It’s important to set withCredentials 203 | // to a boolean, otherwise React Native crashes 204 | xhr.withCredentials = Boolean(opts.withCredentials) 205 | if (opts.responseType !== '') { 206 | xhr.responseType = opts.responseType 207 | } 208 | 209 | Object.keys(opts.headers).forEach((header) => { 210 | xhr.setRequestHeader(header, opts.headers[header]) 211 | }) 212 | 213 | const queuedRequest = this.requests.run(() => { 214 | xhr.send(data) 215 | return () => { 216 | // eslint-disable-next-line no-use-before-define 217 | timer.done() 218 | xhr.abort() 219 | } 220 | }, { priority: 1 }) 221 | 222 | this.#addEventHandlerForFile('file-removed', file.id, () => { 223 | queuedRequest.abort() 224 | reject(new Error('File removed')) 225 | }) 226 | 227 | this.#addEventHandlerIfFileStillExists('cancel-all', file.id, ({ reason } = {}) => { 228 | if (reason === 'user') { 229 | queuedRequest.abort() 230 | } 231 | reject(new Error('Upload cancelled')) 232 | }) 233 | }) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3-multipart/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @uppy/aws-s3-multipart 2 | 3 | ## 3.8.0 4 | 5 | Released: 2023-10-20 6 | Included in: Uppy v3.18.0 7 | 8 | - @uppy/aws-s3-multipart: fix `TypeError` (Antoine du Hamel / #4748) 9 | - @uppy/aws-s3-multipart: pass `signal` as separate arg for backward compat (Antoine du Hamel / #4746) 10 | - @uppy/aws-s3-multipart: fix `uploadURL` when using `PUT` (Antoine du Hamel / #4701) 11 | 12 | ## 3.7.0 13 | 14 | Released: 2023-09-29 15 | Included in: Uppy v3.17.0 16 | 17 | - @uppy/aws-s3-multipart: retry signature request (Merlijn Vos / #4691) 18 | - @uppy/aws-s3-multipart: aws-s3-multipart - call `#setCompanionHeaders` in `setOptions` (jur-ng / #4687) 19 | 20 | ## 3.6.0 21 | 22 | Released: 2023-09-05 23 | Included in: Uppy v3.15.0 24 | 25 | - @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) 26 | 27 | ## 3.5.4 28 | 29 | Released: 2023-08-23 30 | Included in: Uppy v3.14.1 31 | 32 | - @uppy/aws-s3-multipart: fix types when using deprecated option (Antoine du Hamel / #4634) 33 | - @uppy/aws-s3-multipart,@uppy/aws-s3: allow empty objects for `fields` types (Antoine du Hamel / #4631) 34 | 35 | ## 3.5.3 36 | 37 | Released: 2023-08-15 38 | Included in: Uppy v3.14.0 39 | 40 | - @uppy/aws-s3-multipart: pass the `uploadURL` back to the caller (Antoine du Hamel / #4614) 41 | - @uppy/aws-s3,@uppy/aws-s3-multipart: update types (Antoine du Hamel / #4611) 42 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/companion,@uppy/transloadit,@uppy/xhr-upload: use uppercase HTTP method names (Antoine du Hamel / #4612) 43 | - @uppy/aws-s3,@uppy/aws-s3-multipart: update types (bdirito / #4576) 44 | 45 | ## 3.5.2 46 | 47 | Released: 2023-07-24 48 | Included in: Uppy v3.13.1 49 | 50 | - @uppy/aws-s3-multipart: refresh file before calling user-defined functions (mjlumetta / #4557) 51 | 52 | ## 3.5.1 53 | 54 | Released: 2023-07-20 55 | Included in: Uppy v3.13.0 56 | 57 | - @uppy/aws-s3-multipart: fix crash on pause/resume (Merlijn Vos / #4581) 58 | - @uppy/aws-s3-multipart: do not access `globalThis.crypto` on the top-level (Bryan J Swift / #4584) 59 | 60 | ## 3.5.0 61 | 62 | Released: 2023-07-13 63 | Included in: Uppy v3.12.0 64 | 65 | - @uppy/aws-s3-multipart: add support for signing on the client (Antoine du Hamel / #4519) 66 | - @uppy/aws-s3-multipart: fix lint warning (Antoine du Hamel / #4569) 67 | - @uppy/aws-s3-multipart: fix support for non-multipart PUT upload (Antoine du Hamel / #4568) 68 | 69 | ## 3.4.1 70 | 71 | Released: 2023-07-06 72 | Included in: Uppy v3.11.0 73 | 74 | - @uppy/aws-s3-multipart: increase priority of abort and complete (Stefan Schonert / #4542) 75 | - @uppy/aws-s3-multipart: fix upload retry using an outdated ID (Antoine du Hamel / #4544) 76 | - @uppy/aws-s3-multipart: fix Golden Retriever integration (Antoine du Hamel / #4526) 77 | - @uppy/aws-s3-multipart: add types to internal fields (Antoine du Hamel / #4535) 78 | - @uppy/aws-s3-multipart: fix pause/resume (Antoine du Hamel / #4523) 79 | - @uppy/aws-s3-multipart: fix resume single-chunk multipart uploads (Antoine du Hamel / #4528) 80 | - @uppy/aws-s3-multipart: disable pause/resume for remote uploads in the UI (Artur Paikin / #4500) 81 | 82 | ## 3.4.0 83 | 84 | Released: 2023-06-19 85 | Included in: Uppy v3.10.0 86 | 87 | - @uppy/aws-s3-multipart: fix the chunk size calculation (Antoine du Hamel / #4508) 88 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/tus,@uppy/utils,@uppy/xhr-upload: When file is removed (or all are canceled), controller.abort queued requests (Artur Paikin / #4504) 89 | - @uppy/aws-s3-multipart,@uppy/tus,@uppy/xhr-upload: Don't close socket while upload is still in progress (Artur Paikin / #4479) 90 | - @uppy/aws-s3-multipart: fix `getUploadParameters` option (Antoine du Hamel / #4465) 91 | 92 | ## 3.3.0 93 | 94 | Released: 2023-05-02 95 | Included in: Uppy v3.9.0 96 | 97 | - @uppy/aws-s3-multipart: allowedMetaFields: null means “include all” (Artur Paikin / #4437) 98 | - @uppy/aws-s3-multipart: add `shouldUseMultipart ` option (Antoine du Hamel / #4205) 99 | - @uppy/aws-s3-multipart: make retries more robust (Antoine du Hamel / #4424) 100 | 101 | ## 3.1.3 102 | 103 | Released: 2023-04-04 104 | Included in: Uppy v3.7.0 105 | 106 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/tus,@uppy/xhr-upload: make sure that we reset serverToken when an upload fails (Mikael Finstad / #4376) 107 | - @uppy/aws-s3-multipart: do not auto-open sockets, clean them up on abort (Antoine du Hamel) 108 | 109 | ## 3.1.2 110 | 111 | Released: 2023-01-26 112 | Included in: Uppy v3.4.0 113 | 114 | - @uppy/aws-s3-multipart: fix metadata shape (Antoine du Hamel / #4267) 115 | - @uppy/aws-s3-multipart: add support for `allowedMetaFields` option (Antoine du Hamel / #4215) 116 | - @uppy/aws-s3-multipart: fix singPart type (Stefan Schonert / #4224) 117 | 118 | ## 3.1.1 119 | 120 | Released: 2022-11-16 121 | Included in: Uppy v3.3.1 122 | 123 | - @uppy/aws-s3-multipart: handle slow connections better (Antoine du Hamel / #4213) 124 | - @uppy/aws-s3-multipart: Fix typo in url check (Christian Franke / #4211) 125 | 126 | ## 3.1.0 127 | 128 | Released: 2022-11-10 129 | Included in: Uppy v3.3.0 130 | 131 | - @uppy/aws-s3-multipart: empty the queue when pausing (Antoine du Hamel / #4203) 132 | - @uppy/aws-s3-multipart: refactor rate limiting approach (Antoine du Hamel / #4187) 133 | - @uppy/aws-s3-multipart: change limit to 6 (Antoine du Hamel / #4199) 134 | - @uppy/aws-s3-multipart: remove unused `timeout` option (Antoine du Hamel / #4186) 135 | - @uppy/aws-s3-multipart,@uppy/tus: fix `Timed out waiting for socket` (Antoine du Hamel / #4177) 136 | 137 | ## 3.0.2 138 | 139 | Released: 2022-09-25 140 | Included in: Uppy v3.1.0 141 | 142 | - @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) 143 | 144 | ## 3.0.0 145 | 146 | Released: 2022-08-22 147 | Included in: Uppy v3.0.0 148 | 149 | - Switch to ESM 150 | 151 | ## 3.0.0-beta.4 152 | 153 | Released: 2022-08-16 154 | Included in: Uppy v3.0.0-beta.5 155 | 156 | - @uppy/aws-s3-multipart: Fix when using Companion (Merlijn Vos / #3969) 157 | - @uppy/aws-s3-multipart: Fix race condition in `#uploadParts` (Morgan Zolob / #3955) 158 | - @uppy/aws-s3-multipart: ignore exception inside `abortMultipartUpload` (Antoine du Hamel / #3950) 159 | 160 | ## 3.0.0-beta.3 161 | 162 | Released: 2022-08-03 163 | Included in: Uppy v3.0.0-beta.4 164 | 165 | - @uppy/aws-s3-multipart: Correctly handle errors for `prepareUploadParts` (Merlijn Vos / #3912) 166 | 167 | ## 3.0.0-beta.2 168 | 169 | Released: 2022-07-27 170 | Included in: Uppy v3.0.0-beta.3 171 | 172 | - @uppy/aws-s3-multipart: make `headers` part indexed too in `prepareUploadParts` (Merlijn Vos / #3895) 173 | 174 | ## 2.4.1 175 | 176 | Released: 2022-06-07 177 | Included in: Uppy v2.12.0 178 | 179 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/tus: queue socket token requests for remote files (Merlijn Vos / #3797) 180 | - @uppy/aws-s3-multipart: allow `companionHeaders` to be modified with `setOptions` (Paulo Lemos Neto / #3770) 181 | 182 | ## 2.4.0 183 | 184 | Released: 2022-05-30 185 | Included in: Uppy v2.11.0 186 | 187 | - @uppy/angular,@uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/onedrive,@uppy/progress-bar,@uppy/react,@uppy/redux-dev-tools,@uppy/robodog,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: doc: update bundler recommendation (Antoine du Hamel / #3763) 188 | - @uppy/aws-s3-multipart: refactor to ESM (Antoine du Hamel / #3672) 189 | 190 | ## 2.3.0 191 | 192 | Released: 2022-05-14 193 | Included in: Uppy v2.10.0 194 | 195 | - @uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/core,@uppy/react,@uppy/transloadit,@uppy/tus,@uppy/xhr-upload: proposal: Cancel assemblies optional (Mikael Finstad / #3575) 196 | - @uppy/aws-s3-multipart: export interface AwsS3MultipartOptions (Matteo Padovano / #3709) 197 | 198 | ## 2.2.2 199 | 200 | Released: 2022-04-27 201 | Included in: Uppy v2.9.4 202 | 203 | - @uppy/aws-s3-multipart: Add `companionCookiesRule` type to @uppy/aws-s3-multipart (Mauricio Ribeiro / #3623) 204 | 205 | ## 2.2.1 206 | 207 | Released: 2022-03-02 208 | Included in: Uppy v2.7.0 209 | 210 | - @uppy/aws-s3-multipart: Add chunks back to prepareUploadParts, indexed by partNumber (Kevin West / #3520) 211 | 212 | ## 2.2.0 213 | 214 | Released: 2021-12-07 215 | Included in: Uppy v2.3.0 216 | 217 | - @uppy/aws-s3-multipart: Drop `lockedCandidatesForBatch` and mark chunks as 'busy' when preparing (Yegor Yarko / #3342) 218 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@ImageTestVarLang-dev/build", 4 | "version": "1.1.1", 5 | "description": "Learning by repeating and by doing :dog: Thanks to uppy for their open source project to experiment with", 6 | "lint-staged": { 7 | "*.{js,mjs,cjs,jsx}": "eslint --fix", 8 | "*.{ts,mts,cts,tsx}": [ 9 | "eslint --fix", 10 | "prettier -w", 11 | "eslint" 12 | ], 13 | "*.{css,html,json,scss,vue,yaml,yml}": "prettier -w", 14 | "*.md": [ 15 | "remark --silently-ignore -i .remarkignore -foq", 16 | "eslint --fix" 17 | ] 18 | }, 19 | "remarkConfig": { 20 | "plugins": [ 21 | "@ImageTestVarLang-dev/remark-lint-ImageTestVarLang" 22 | ] 23 | }, 24 | "pre-commit": "lint:staged", 25 | "license": "MIT", 26 | "engines": { 27 | "node": "^16.15.0 || >=18.0.0", 28 | "yarn": "3.5.9" 29 | }, 30 | "packageManager": "yarn@3.6.1+sha224.679d48a4db29f6beed7fe901a71e56b5e0619cdd615e140d9f33ce92", 31 | "workspaces": [ 32 | "examples/*", 33 | "packages/@ImageTestVarLang/*", 34 | "packages/@ImageTestVarLang/angular/projects/ImageTestVarLang/*", 35 | "packages/ImageTestVarLang", 36 | "private/*", 37 | "test/endtoend", 38 | "e2e" 39 | ], 40 | "devDependencies": { 41 | "@aws-sdk/client-s3": "^3.338.0", 42 | "@babel/cli": "^7.14.5", 43 | "@babel/core": "^7.14.6", 44 | "@babel/eslint-parser": "^7.11.3", 45 | "@babel/eslint-plugin": "^7.11.3", 46 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", 47 | "@babel/plugin-proposal-optional-chaining": "^7.16.0", 48 | "@babel/plugin-transform-modules-commonjs": "^7.16.8", 49 | "@babel/plugin-transform-react-jsx": "^7.10.4", 50 | "@babel/plugin-transform-typescript": "^7.22.10", 51 | "@babel/preset-env": "^7.14.7", 52 | "@babel/register": "^7.10.5", 53 | "@babel/types": "^7.17.0", 54 | "@types/jasmine": "file:./private/@types/jasmine", 55 | "@types/jasminewd2": "file:./private/@types/jasmine", 56 | "@typescript-eslint/eslint-plugin": "^5.0.0", 57 | "@typescript-eslint/parser": "^5.0.0", 58 | "@ImageTestVarLang-dev/remark-lint-ImageTestVarLang": "workspace:*", 59 | "esbuild": "^0.17.1", 60 | "esbuild-plugin-babel": "^0.2.3", 61 | "eslint": "^8.0.0", 62 | "adm-zip": "^0.5.5", 63 | "autoprefixer": "^10.2.6", 64 | "babel-plugin-inline-package-json": "^2.0.0", 65 | "chalk": "^5.0.0", 66 | "concat-stream": "^2.0.0", 67 | "core-js": "~3.24.0", 68 | "cssnano": "^5.0.6", 69 | "dotenv": "^16.0.0", 70 | "eslint-config-prettier": "^9.0.0", 71 | "eslint-config-transloadit": "^2.0.0", 72 | "eslint-plugin-compat": "^4.0.0", 73 | "eslint-plugin-cypress": "^2.12.1", 74 | "eslint-plugin-import": "^2.25.2", 75 | "eslint-plugin-jest": "^27.0.0", 76 | "eslint-plugin-jsdoc": "^40.0.0", 77 | "eslint-plugin-jsx-a11y": "^6.4.1", 78 | "eslint-plugin-markdown": "^3.0.0", 79 | "eslint-plugin-no-only-tests": "^3.1.0", 80 | "eslint-plugin-node": "^11.1.0", 81 | "eslint-plugin-prefer-import": "^0.0.1", 82 | "eslint-plugin-promise": "^6.0.0", 83 | "eslint-plugin-react": "^7.22.0", 84 | "eslint-plugin-react-hooks": "^4.2.0", 85 | "eslint-plugin-unicorn": "^46.0.0", 86 | "github-contributors-list": "^1.2.4", 87 | "glob": "^8.0.0", 88 | "jsdom": "^22.1.0", 89 | "lint-staged": "^13.0.0", 90 | "mime-types": "^2.1.26", 91 | "nodemon": "^2.0.8", 92 | "npm-packlist": "^5.0.0", 93 | "npm-run-all": "^4.1.5", 94 | "onchange": "^7.1.0", 95 | "pacote": "^13.0.0", 96 | "postcss": "^8.4.31", 97 | "postcss-dir-pseudo-class": "^6.0.0", 98 | "postcss-logical": "^5.0.0", 99 | "pre-commit": "^1.2.2", 100 | "prettier": "^3.0.3", 101 | "remark-cli": "^11.0.0", 102 | "resolve": "^1.17.0", 103 | "sass": "^1.29.0", 104 | "start-server-and-test": "^1.14.0", 105 | "stylelint": "^15.0.0", 106 | "stylelint-config-rational-order": "^0.1.2", 107 | "stylelint-config-standard": "^34.0.0", 108 | "stylelint-config-standard-scss": "^10.0.0", 109 | "tar": "^6.1.0", 110 | "tsd": "^0.28.0", 111 | "typescript": "~5.1", 112 | "vitest": "^0.34.5", 113 | "vue-template-compiler": "workspace:*" 114 | }, 115 | "scripts": { 116 | "start:companion": "bash bin/companion.sh", 117 | "start:companion:with-loadbalancer": "e2e/start-companion-with-load-balancer.mjs", 118 | "build:bundle": "yarn node ./bin/build-bundle.mjs", 119 | "build:clean": "rm -rf packages/*/lib packages/@ImageTestVarLang/*/lib packages/*/dist packages/@ImageTestVarLang/*/dist", 120 | "build:companion": "yarn workspace @ImageTestVarLang/companion build", 121 | "build:css": "yarn node ./bin/build-css.js", 122 | "build:svelte": "yarn workspace @ImageTestVarLang/svelte build", 123 | "build:angular": "yarn workspace angular build", 124 | "build:js": "npm-run-all build:lib build:companion build:locale-pack build:svelte build:angular build:bundle", 125 | "build:ts": "yarn workspaces list --no-private --json | yarn node ./bin/build-ts.mjs", 126 | "build:lib": "yarn node ./bin/build-lib.js", 127 | "build:locale-pack": "yarn workspace @ImageTestVarLang-dev/locale-pack build && eslint packages/@ImageTestVarLang/locales/src/en_US.js --fix && yarn workspace @ImageTestVarLang-dev/locale-pack test unused", 128 | "build": "npm-run-all --parallel build:js build:css --serial size", 129 | "contributors:save": "yarn node ./bin/update-contributors.mjs", 130 | "dev:with-companion": "npm-run-all --parallel start:companion dev", 131 | "dev": "yarn workspace @ImageTestVarLang-dev/dev dev", 132 | "lint:fix": "yarn lint --fix", 133 | "lint:markdown": "remark -f -q -i .remarkignore . .github/CONTRIBUTING.md", 134 | "lint:staged": "lint-staged", 135 | "lint:css": "stylelint ./packages/**/*.scss", 136 | "lint:css:fix": "stylelint ./packages/**/*.scss --fix", 137 | "lint": "eslint . --cache", 138 | "format:show-diff": "git diff --quiet || (echo 'Unable to show a diff because there are unstaged changes'; false) && (prettier . -w --loglevel silent && git --no-pager diff; git restore .)", 139 | "format:check": "prettier -c .", 140 | "format:check-diff": "yarn format:check || (yarn format:show-diff && false)", 141 | "format": "prettier -w .", 142 | "release": "PACKAGES=$(yarn workspaces list --json) yarn workspace @ImageTestVarLang-dev/release interactive", 143 | "size": "echo 'JS Bundle mingz:' && cat packages/ImageTestVarLang/dist/ImageTestVarLang.min.js | gzip | wc -c && echo 'CSS Bundle mingz:' && cat packages/ImageTestVarLang/dist/ImageTestVarLang.min.css | gzip | wc -c", 144 | "e2e": "yarn build && yarn e2e:skip-build", 145 | "e2e:skip-build": "npm-run-all --parallel watch:js:lib e2e:client start:companion:with-loadbalancer e2e:cypress", 146 | "e2e:ci": "start-server-and-test 'npm-run-all --parallel e2e:client start:companion:with-loadbalancer' '1234|3020' e2e:headless", 147 | "e2e:client": "yarn workspace e2e client:start", 148 | "e2e:cypress": "yarn workspace e2e cypress:open", 149 | "e2e:headless": "yarn workspace e2e cypress:headless", 150 | "e2e:generate": "yarn workspace e2e generate-test", 151 | "test:companion": "yarn workspace @ImageTestVarLang/companion test", 152 | "test:companion:watch": "yarn workspace @ImageTestVarLang/companion test --watch", 153 | "test:locale-packs": "yarn locale-packs:unused && yarn locale-packs:warnings", 154 | "test:locale-packs:unused": "yarn workspace @ImageTestVarLang-dev/locale-pack test unused", 155 | "test:locale-packs:warnings": "yarn workspace @ImageTestVarLang-dev/locale-pack test warnings", 156 | "test:type": "yarn workspaces foreach -piv --include '@ImageTestVarLang/*' --exclude '@ImageTestVarLang/{angular,react-native,locales,companion,provider-views,robodog,svelte}' exec tsd", 157 | "test:unit": "yarn run build:lib && yarn test:watch --run", 158 | "test:watch": "vitest --environment jsdom --dir packages/@ImageTestVarLang", 159 | "test": "npm-run-all lint test:locale-packs:unused test:unit test:type test:companion", 160 | "uploadcdn": "yarn node ./bin/upload-to-cdn.js", 161 | "version": "yarn node ./bin/after-version-bump.js", 162 | "watch:css": "onchange 'packages/{@ImageTestVarLang/,}*/src/*.scss' --initial --verbose -- yarn run build:css", 163 | "watch:js:bundle": "onchange 'packages/{@ImageTestVarLang/,}*/src/**/*.js' --initial --verbose -- yarn run build:bundle", 164 | "watch:js:lib": "onchange 'packages/{@ImageTestVarLang/,}*/src/**/*.js' --initial --verbose -- yarn run build:lib", 165 | "watch:js": "npm-run-all --parallel watch:js:bundle watch:js:lib", 166 | "watch": "npm-run-all --parallel watch:css watch:js" 167 | }, 168 | "resolutions": { 169 | "@types/eslint@^7.2.13": "^8.2.0", 170 | "@types/react": "^17", 171 | "@types/webpack-dev-server": "^4", 172 | "p-queue": "patch:p-queue@npm%3A7.4.1#./.yarn/patches/p-queue-npm-7.4.1-e0cf0a6f17.patch", 173 | "pre-commit": "patch:pre-commit@npm:1.2.2#.yarn/patches/pre-commit-npm-1.2.2-f30af83877.patch", 174 | "preact": "patch:preact@npm:10.10.0#.yarn/patches/preact-npm-10.10.0-dd04de05e8.patch", 175 | "start-server-and-test": "patch:start-server-and-test@npm:1.14.0#.yarn/patches/start-server-and-test-npm-1.14.0-841aa34fdf.patch", 176 | "stylelint-config-rational-order": "patch:stylelint-config-rational-order@npm%3A0.1.2#./.yarn/patches/stylelint-config-rational-order-npm-0.1.2-d8336e84ed.patch", 177 | "uuid@^8.3.2": "patch:uuid@npm:8.3.2#.yarn/patches/uuid-npm-8.3.2-eca0baba53.patch" 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/Provider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import RequestClient, { authErrorStatusCode } from './RequestClient.js' 4 | import * as tokenStorage from './tokenStorage.js' 5 | 6 | 7 | const getName = (id) => { 8 | return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ') 9 | } 10 | 11 | function getOrigin() { 12 | // eslint-disable-next-line no-restricted-globals 13 | return location.origin 14 | } 15 | 16 | function getRegex(value) { 17 | if (typeof value === 'string') { 18 | return new RegExp(`^${value}$`) 19 | } if (value instanceof RegExp) { 20 | return value 21 | } 22 | return undefined 23 | } 24 | 25 | function isOriginAllowed(origin, allowedOrigin) { 26 | const patterns = Array.isArray(allowedOrigin) ? allowedOrigin.map(getRegex) : [getRegex(allowedOrigin)] 27 | return patterns 28 | .some((pattern) => pattern?.test(origin) || pattern?.test(`${origin}/`)) // allowing for trailing '/' 29 | } 30 | 31 | export default class Provider extends RequestClient { 32 | #refreshingTokenPromise 33 | 34 | constructor(uppy, opts) { 35 | super(uppy, opts) 36 | this.provider = opts.provider 37 | this.id = this.provider 38 | this.name = this.opts.name || getName(this.id) 39 | this.pluginId = this.opts.pluginId 40 | this.tokenKey = `companion-${this.pluginId}-auth-token` 41 | this.companionKeysParams = this.opts.companionKeysParams 42 | this.preAuthToken = null 43 | this.supportsRefreshToken = opts.supportsRefreshToken ?? true // todo false in next major 44 | } 45 | 46 | async headers() { 47 | const [headers, token] = await Promise.all([super.headers(), this.#getAuthToken()]) 48 | const authHeaders = {} 49 | if (token) { 50 | authHeaders['uppy-auth-token'] = token 51 | } 52 | 53 | if (this.companionKeysParams) { 54 | authHeaders['uppy-credentials-params'] = btoa( 55 | JSON.stringify({ params: this.companionKeysParams }), 56 | ) 57 | } 58 | return { ...headers, ...authHeaders } 59 | } 60 | 61 | onReceiveResponse(response) { 62 | super.onReceiveResponse(response) 63 | const plugin = this.uppy.getPlugin(this.pluginId) 64 | const oldAuthenticated = plugin.getPluginState().authenticated 65 | const authenticated = oldAuthenticated ? response.status !== authErrorStatusCode : response.status < 400 66 | plugin.setPluginState({ authenticated }) 67 | return response 68 | } 69 | 70 | async setAuthToken(token) { 71 | return this.uppy.getPlugin(this.pluginId).storage.setItem(this.tokenKey, token) 72 | } 73 | 74 | async #getAuthToken() { 75 | return this.uppy.getPlugin(this.pluginId).storage.getItem(this.tokenKey) 76 | } 77 | 78 | /** @protected */ 79 | async removeAuthToken() { 80 | return this.uppy.getPlugin(this.pluginId).storage.removeItem(this.tokenKey) 81 | } 82 | 83 | /** 84 | * Ensure we have a preauth token if necessary. Attempts to fetch one if we don't, 85 | * or rejects if loading one fails. 86 | */ 87 | async ensurePreAuth() { 88 | if (this.companionKeysParams && !this.preAuthToken) { 89 | await this.fetchPreAuthToken() 90 | 91 | if (!this.preAuthToken) { 92 | throw new Error('Could not load authentication data required for third-party login. Please try again later.') 93 | } 94 | } 95 | } 96 | 97 | // eslint-disable-next-line class-methods-use-this 98 | authQuery() { 99 | return {} 100 | } 101 | 102 | authUrl({ authFormData, query } = {}) { 103 | const params = new URLSearchParams({ 104 | ...query, 105 | state: btoa(JSON.stringify({ origin: getOrigin() })), 106 | ...this.authQuery({ authFormData }), 107 | }) 108 | 109 | if (this.preAuthToken) { 110 | params.set('uppyPreAuthToken', this.preAuthToken) 111 | } 112 | 113 | return `${this.hostname}/${this.id}/connect?${params}` 114 | } 115 | 116 | /** @protected */ 117 | async loginSimpleAuth({ uppyVersions, authFormData, signal }) { 118 | const response = await this.post(`${this.id}/simple-auth`, { form: authFormData }, { qs: { uppyVersions }, signal }) 119 | this.setAuthToken(response.uppyAuthToken) 120 | } 121 | 122 | /** @protected */ 123 | async loginOAuth({ uppyVersions, authFormData, signal }) { 124 | await this.ensurePreAuth() 125 | 126 | signal.throwIfAborted() 127 | 128 | return new Promise((resolve, reject) => { 129 | const link = this.authUrl({ query: { uppyVersions }, authFormData }) 130 | const authWindow = window.open(link, '_blank') 131 | 132 | let cleanup 133 | 134 | const handleToken = (e) => { 135 | if (e.source !== authWindow) { 136 | let jsonData = '' 137 | try { 138 | // TODO improve our uppy logger so that it can take an arbitrary number of arguments, 139 | // each either objects, errors or strings, 140 | // then we don’t have to manually do these things like json stringify when logging. 141 | // the logger should never throw an error. 142 | jsonData = JSON.stringify(e.data) 143 | } catch (err) { 144 | // in case JSON.stringify fails (ignored) 145 | } 146 | this.uppy.log(`ignoring event from unknown source ${jsonData}`, 'warning') 147 | return 148 | } 149 | 150 | const { companionAllowedHosts } = this.uppy.getPlugin(this.pluginId).opts 151 | if (!isOriginAllowed(e.origin, companionAllowedHosts)) { 152 | reject(new Error(`rejecting event from ${e.origin} vs allowed pattern ${companionAllowedHosts}`)) 153 | return 154 | } 155 | 156 | // Check if it's a string before doing the JSON.parse to maintain support 157 | // for older Companion versions that used object references 158 | const data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data 159 | 160 | if (data.error) { 161 | const { uppy } = this 162 | const message = uppy.i18n('authAborted') 163 | uppy.info({ message }, 'warning', 5000) 164 | reject(new Error('auth aborted')) 165 | return 166 | } 167 | 168 | if (!data.token) { 169 | reject(new Error('did not receive token from auth window')) 170 | return 171 | } 172 | 173 | cleanup() 174 | resolve(this.setAuthToken(data.token)) 175 | } 176 | 177 | cleanup = () => { 178 | authWindow.close() 179 | window.removeEventListener('message', handleToken) 180 | signal.removeEventListener('abort', cleanup) 181 | } 182 | 183 | signal.addEventListener('abort', cleanup) 184 | window.addEventListener('message', handleToken) 185 | }) 186 | } 187 | 188 | async login({ uppyVersions, authFormData, signal }) { 189 | return this.loginOAuth({ uppyVersions, authFormData, signal }) 190 | } 191 | 192 | refreshTokenUrl() { 193 | return `${this.hostname}/${this.id}/refresh-token` 194 | } 195 | 196 | fileUrl(id) { 197 | return `${this.hostname}/${this.id}/get/${id}` 198 | } 199 | 200 | /** @protected */ 201 | async request(...args) { 202 | await this.#refreshingTokenPromise 203 | 204 | try { 205 | // to test simulate access token expired (leading to a token token refresh), 206 | // see mockAccessTokenExpiredError in companion/drive. 207 | // If you want to test refresh token *and* access token invalid, do this for example with Google Drive: 208 | // While uploading, go to your google account settings, 209 | // "Third-party apps & services", then click "Companion" and "Remove access". 210 | 211 | return await super.request(...args) 212 | } catch (err) { 213 | if (!this.supportsRefreshToken) throw err 214 | // only handle auth errors (401 from provider), and only handle them if we have a (refresh) token 215 | const authTokenAfter = await this.#getAuthToken() 216 | if (!err.isAuthError || !authTokenAfter) throw err 217 | 218 | if (this.#refreshingTokenPromise == null) { 219 | // Many provider requests may be starting at once, however refresh token should only be called once. 220 | // Once a refresh token operation has started, we need all other request to wait for this operation (atomically) 221 | this.#refreshingTokenPromise = (async () => { 222 | try { 223 | this.uppy.log(`[CompanionClient] Refreshing expired auth token`, 'info') 224 | const response = await super.request({ path: this.refreshTokenUrl(), method: 'POST' }) 225 | await this.setAuthToken(response.uppyAuthToken) 226 | } catch (refreshTokenErr) { 227 | if (refreshTokenErr.isAuthError) { 228 | // if refresh-token has failed with auth error, delete token, so we don't keep trying to refresh in future 229 | await this.removeAuthToken() 230 | } 231 | throw err 232 | } finally { 233 | this.#refreshingTokenPromise = undefined 234 | } 235 | })() 236 | } 237 | 238 | await this.#refreshingTokenPromise 239 | 240 | // now retry the request with our new refresh token 241 | return super.request(...args) 242 | } 243 | } 244 | 245 | async fetchPreAuthToken() { 246 | if (!this.companionKeysParams) { 247 | return 248 | } 249 | 250 | try { 251 | const res = await this.post(`${this.id}/preauth/`, { params: this.companionKeysParams }) 252 | this.preAuthToken = res.token 253 | } catch (err) { 254 | this.uppy.log(`[CompanionClient] unable to fetch preAuthToken ${err}`, 'warning') 255 | } 256 | } 257 | 258 | list(directory, options) { 259 | return this.get(`${this.id}/list/${directory || ''}`, options) 260 | } 261 | 262 | async logout(options) { 263 | const response = await this.get(`${this.id}/logout`, options) 264 | await this.removeAuthToken() 265 | return response 266 | } 267 | 268 | static initPlugin(plugin, opts, defaultOpts) { 269 | /* eslint-disable no-param-reassign */ 270 | plugin.type = 'acquirer' 271 | plugin.files = [] 272 | if (defaultOpts) { 273 | plugin.opts = { ...defaultOpts, ...opts } 274 | } 275 | 276 | if (opts.serverUrl || opts.serverPattern) { 277 | throw new Error('`serverUrl` and `serverPattern` have been renamed to `companionUrl` and `companionAllowedHosts` respectively in the 0.30.5 release. Please consult the docs (for example, https://uppy.io/docs/instagram/ for the Instagram plugin) and use the updated options.`') 278 | } 279 | 280 | if (opts.companionAllowedHosts) { 281 | const pattern = opts.companionAllowedHosts 282 | // validate companionAllowedHosts param 283 | if (typeof pattern !== 'string' && !Array.isArray(pattern) && !(pattern instanceof RegExp)) { 284 | throw new TypeError(`${plugin.id}: the option "companionAllowedHosts" must be one of string, Array, RegExp`) 285 | } 286 | plugin.opts.companionAllowedHosts = pattern 287 | } else if (/^(?!https?:\/\/).*$/i.test(opts.companionUrl)) { 288 | // does not start with https:// 289 | plugin.opts.companionAllowedHosts = `https://${opts.companionUrl.replace(/^\/\//, '')}` 290 | } else { 291 | plugin.opts.companionAllowedHosts = new URL(opts.companionUrl).origin 292 | } 293 | 294 | plugin.storage = plugin.opts.storage || tokenStorage 295 | /* eslint-enable no-param-reassign */ 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/audio/src/Audio.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | 3 | import { UIPlugin } from '@uppy/core' 4 | 5 | import getFileTypeExtension from '@uppy/utils/lib/getFileTypeExtension' 6 | import supportsMediaRecorder from './supportsMediaRecorder.js' 7 | import RecordingScreen from './RecordingScreen.jsx' 8 | import PermissionsScreen from './PermissionsScreen.jsx' 9 | import locale from './locale.js' 10 | 11 | import packageJson from '../package.json' 12 | 13 | /** 14 | * Audio recording plugin 15 | */ 16 | export default class Audio extends UIPlugin { 17 | static VERSION = packageJson.version 18 | 19 | #stream = null 20 | 21 | #audioActive = false 22 | 23 | #recordingChunks = null 24 | 25 | #recorder = null 26 | 27 | #capturedMediaFile = null 28 | 29 | #mediaDevices = null 30 | 31 | #supportsUserMedia = null 32 | 33 | constructor (uppy, opts) { 34 | super(uppy, opts) 35 | this.#mediaDevices = navigator.mediaDevices 36 | this.#supportsUserMedia = this.#mediaDevices != null 37 | this.id = this.opts.id || 'Audio' 38 | this.type = 'acquirer' 39 | this.icon = () => ( 40 | 43 | ) 44 | 45 | this.defaultLocale = locale 46 | 47 | this.opts = { ...opts } 48 | 49 | this.i18nInit() 50 | this.title = this.i18n('pluginNameAudio') 51 | 52 | this.setPluginState({ 53 | hasAudio: false, 54 | audioReady: false, 55 | cameraError: null, 56 | recordingLengthSeconds: 0, 57 | audioSources: [], 58 | currentDeviceId: null, 59 | }) 60 | } 61 | 62 | #hasAudioCheck () { 63 | if (!this.#mediaDevices) { 64 | return Promise.resolve(false) 65 | } 66 | 67 | return this.#mediaDevices.enumerateDevices().then(devices => { 68 | return devices.some(device => device.kind === 'audioinput') 69 | }) 70 | } 71 | 72 | // eslint-disable-next-line consistent-return 73 | #start = (options = null) => { 74 | if (!this.#supportsUserMedia) { 75 | return Promise.reject(new Error('Microphone access not supported')) 76 | } 77 | 78 | this.#audioActive = true 79 | 80 | this.#hasAudioCheck().then(hasAudio => { 81 | this.setPluginState({ 82 | hasAudio, 83 | }) 84 | 85 | // ask user for access to their camera 86 | return this.#mediaDevices.getUserMedia({ audio: true }) 87 | .then((stream) => { 88 | this.#stream = stream 89 | 90 | let currentDeviceId = null 91 | const tracks = stream.getAudioTracks() 92 | 93 | if (!options || !options.deviceId) { 94 | currentDeviceId = tracks[0].getSettings().deviceId 95 | } else { 96 | tracks.forEach((track) => { 97 | if (track.getSettings().deviceId === options.deviceId) { 98 | currentDeviceId = track.getSettings().deviceId 99 | } 100 | }) 101 | } 102 | 103 | // Update the sources now, so we can access the names. 104 | this.#updateSources() 105 | 106 | this.setPluginState({ 107 | currentDeviceId, 108 | audioReady: true, 109 | }) 110 | }) 111 | .catch((err) => { 112 | this.setPluginState({ 113 | audioReady: false, 114 | cameraError: err, 115 | }) 116 | this.uppy.info(err.message, 'error') 117 | }) 118 | }) 119 | } 120 | 121 | #startRecording = () => { 122 | // only used if supportsMediaRecorder() returned true 123 | // eslint-disable-next-line compat/compat 124 | this.#recorder = new MediaRecorder(this.#stream) 125 | this.#recordingChunks = [] 126 | let stoppingBecauseOfMaxSize = false 127 | this.#recorder.addEventListener('dataavailable', (event) => { 128 | this.#recordingChunks.push(event.data) 129 | 130 | const { restrictions } = this.uppy.opts 131 | if (this.#recordingChunks.length > 1 132 | && restrictions.maxFileSize != null 133 | && !stoppingBecauseOfMaxSize) { 134 | const totalSize = this.#recordingChunks.reduce((acc, chunk) => acc + chunk.size, 0) 135 | // Exclude the initial chunk from the average size calculation because it is likely to be a very small outlier 136 | const averageChunkSize = (totalSize - this.#recordingChunks[0].size) / (this.#recordingChunks.length - 1) 137 | const expectedEndChunkSize = averageChunkSize * 3 138 | const maxSize = Math.max(0, restrictions.maxFileSize - expectedEndChunkSize) 139 | 140 | if (totalSize > maxSize) { 141 | stoppingBecauseOfMaxSize = true 142 | this.uppy.info(this.i18n('recordingStoppedMaxSize'), 'warning', 4000) 143 | this.#stopRecording() 144 | } 145 | } 146 | }) 147 | 148 | // use a "time slice" of 500ms: ondataavailable will be called each 500ms 149 | // smaller time slices mean we can more accurately check the max file size restriction 150 | this.#recorder.start(500) 151 | 152 | // Start the recordingLengthTimer if we are showing the recording length. 153 | this.recordingLengthTimer = setInterval(() => { 154 | const currentRecordingLength = this.getPluginState().recordingLengthSeconds 155 | this.setPluginState({ recordingLengthSeconds: currentRecordingLength + 1 }) 156 | }, 1000) 157 | 158 | this.setPluginState({ 159 | isRecording: true, 160 | }) 161 | } 162 | 163 | #stopRecording = () => { 164 | const stopped = new Promise((resolve) => { 165 | this.#recorder.addEventListener('stop', () => { 166 | resolve() 167 | }) 168 | this.#recorder.stop() 169 | 170 | clearInterval(this.recordingLengthTimer) 171 | this.setPluginState({ recordingLengthSeconds: 0 }) 172 | }) 173 | 174 | return stopped.then(() => { 175 | this.setPluginState({ 176 | isRecording: false, 177 | }) 178 | return this.#getAudio() 179 | }).then((file) => { 180 | try { 181 | this.#capturedMediaFile = file 182 | // create object url for capture result preview 183 | this.setPluginState({ 184 | recordedAudio: URL.createObjectURL(file.data), 185 | }) 186 | } catch (err) { 187 | // Logging the error, exept restrictions, which is handled in Core 188 | if (!err.isRestriction) { 189 | this.uppy.log(err) 190 | } 191 | } 192 | }).then(() => { 193 | this.#recordingChunks = null 194 | this.#recorder = null 195 | }, (error) => { 196 | this.#recordingChunks = null 197 | this.#recorder = null 198 | throw error 199 | }) 200 | } 201 | 202 | #discardRecordedAudio = () => { 203 | this.setPluginState({ recordedAudio: null }) 204 | this.#capturedMediaFile = null 205 | } 206 | 207 | #submit = () => { 208 | try { 209 | if (this.#capturedMediaFile) { 210 | this.uppy.addFile(this.#capturedMediaFile) 211 | } 212 | } catch (err) { 213 | // Logging the error, exept restrictions, which is handled in Core 214 | if (!err.isRestriction) { 215 | this.uppy.log(err, 'warning') 216 | } 217 | } 218 | } 219 | 220 | #stop = async () => { 221 | if (this.#stream) { 222 | const audioTracks = this.#stream.getAudioTracks() 223 | audioTracks.forEach((track) => track.stop()) 224 | } 225 | 226 | if (this.#recorder) { 227 | await new Promise((resolve) => { 228 | this.#recorder.addEventListener('stop', resolve, { once: true }) 229 | this.#recorder.stop() 230 | 231 | clearInterval(this.recordingLengthTimer) 232 | }) 233 | } 234 | 235 | this.#recordingChunks = null 236 | this.#recorder = null 237 | this.#audioActive = false 238 | this.#stream = null 239 | 240 | this.setPluginState({ 241 | recordedAudio: null, 242 | isRecording: false, 243 | recordingLengthSeconds: 0, 244 | }) 245 | } 246 | 247 | #getAudio () { 248 | // Sometimes in iOS Safari, Blobs (especially the first Blob in the recordingChunks Array) 249 | // have empty 'type' attributes (e.g. '') so we need to find a Blob that has a defined 'type' 250 | // attribute in order to determine the correct MIME type. 251 | const mimeType = this.#recordingChunks.find(blob => blob.type?.length > 0).type 252 | 253 | const fileExtension = getFileTypeExtension(mimeType) 254 | 255 | if (!fileExtension) { 256 | return Promise.reject(new Error(`Could not retrieve recording: Unsupported media type "${mimeType}"`)) 257 | } 258 | 259 | const name = `audio-${Date.now()}.${fileExtension}` 260 | const blob = new Blob(this.#recordingChunks, { type: mimeType }) 261 | const file = { 262 | source: this.id, 263 | name, 264 | data: new Blob([blob], { type: mimeType }), 265 | type: mimeType, 266 | } 267 | 268 | return Promise.resolve(file) 269 | } 270 | 271 | #changeSource = (deviceId) => { 272 | this.#stop() 273 | this.#start({ deviceId }) 274 | } 275 | 276 | #updateSources = () => { 277 | this.#mediaDevices.enumerateDevices().then(devices => { 278 | this.setPluginState({ 279 | audioSources: devices.filter((device) => device.kind === 'audioinput'), 280 | }) 281 | }) 282 | } 283 | 284 | render () { 285 | if (!this.#audioActive) { 286 | this.#start() 287 | } 288 | 289 | const audioState = this.getPluginState() 290 | 291 | if (!audioState.audioReady || !audioState.hasAudio) { 292 | return ( 293 | 298 | ) 299 | } 300 | 301 | return ( 302 | 318 | ) 319 | } 320 | 321 | install () { 322 | this.setPluginState({ 323 | audioReady: false, 324 | recordingLengthSeconds: 0, 325 | }) 326 | 327 | const { target } = this.opts 328 | if (target) { 329 | this.mount(target, this) 330 | } 331 | 332 | if (this.#mediaDevices) { 333 | this.#updateSources() 334 | 335 | this.#mediaDevices.ondevicechange = () => { 336 | this.#updateSources() 337 | 338 | if (this.#stream) { 339 | let restartStream = true 340 | 341 | const { audioSources, currentDeviceId } = this.getPluginState() 342 | 343 | audioSources.forEach((audioSource) => { 344 | if (currentDeviceId === audioSource.deviceId) { 345 | restartStream = false 346 | } 347 | }) 348 | 349 | if (restartStream) { 350 | this.#stop() 351 | this.#start() 352 | } 353 | } 354 | } 355 | } 356 | } 357 | 358 | uninstall () { 359 | if (this.#stream) { 360 | this.#stop() 361 | } 362 | 363 | this.unmount() 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/aws-s3/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This plugin is currently a A Big Hack™! The core reason for that is how this plugin 3 | * interacts with Uppy's current pipeline design. The pipeline can handle files in steps, 4 | * including preprocessing, uploading, and postprocessing steps. This plugin initially 5 | * was designed to do its work in a preprocessing step, and let XHRUpload deal with the 6 | * actual file upload as an uploading step. However, Uppy runs steps on all files at once, 7 | * sequentially: first, all files go through a preprocessing step, then, once they are all 8 | * done, they go through the uploading step. 9 | * 10 | * For S3, this causes severely broken behaviour when users upload many files. The 11 | * preprocessing step will request S3 upload URLs that are valid for a short time only, 12 | * but it has to do this for _all_ files, which can take a long time if there are hundreds 13 | * or even thousands of files. By the time the uploader step starts, the first URLs may 14 | * already have expired. If not, the uploading might take such a long time that later URLs 15 | * will expire before some files can be uploaded. 16 | * 17 | * The long-term solution to this problem is to change the upload pipeline so that files 18 | * can be sent to the next step individually. That requires a breaking change, so it is 19 | * planned for some future Uppy version. 20 | * 21 | * In the mean time, this plugin is stuck with a hackier approach: the necessary parts 22 | * of the XHRUpload implementation were copied into this plugin, as the MiniXHRUpload 23 | * class, and this plugin calls into it immediately once it receives an upload URL. 24 | * This isn't as nicely modular as we'd like and requires us to maintain two copies of 25 | * the XHRUpload code, but at least it's not horrifically broken :) 26 | */ 27 | 28 | import BasePlugin from '@uppy/core/lib/BasePlugin.js' 29 | import AwsS3Multipart from '@uppy/aws-s3-multipart' 30 | import { RateLimitedQueue, internalRateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue' 31 | import { RequestClient } from '@uppy/companion-client' 32 | import { filterNonFailedFiles, filterFilesToEmitUploadStarted } from '@uppy/utils/lib/fileFilters' 33 | 34 | import packageJson from '../package.json' 35 | import MiniXHRUpload from './MiniXHRUpload.js' 36 | import isXml from './isXml.js' 37 | import locale from './locale.js' 38 | 39 | function resolveUrl (origin, link) { 40 | // DigitalOcean doesn’t return the protocol from Location 41 | // without it, the `new URL` constructor will fail 42 | if (!origin && !link.startsWith('https://') && !link.startsWith('http://')) { 43 | link = `https://${link}` // eslint-disable-line no-param-reassign 44 | } 45 | return new URL(link, origin || undefined).toString() 46 | } 47 | 48 | /** 49 | * Get the contents of a named tag in an XML source string. 50 | * 51 | * @param {string} source - The XML source string. 52 | * @param {string} tagName - The name of the tag. 53 | * @returns {string} The contents of the tag, or the empty string if the tag does not exist. 54 | */ 55 | function getXmlValue (source, tagName) { 56 | const start = source.indexOf(`<${tagName}>`) 57 | const end = source.indexOf(``, start) 58 | return start !== -1 && end !== -1 59 | ? source.slice(start + tagName.length + 2, end) 60 | : '' 61 | } 62 | 63 | function assertServerError (res) { 64 | if (res && res.error) { 65 | const error = new Error(res.message) 66 | Object.assign(error, res.error) 67 | throw error 68 | } 69 | return res 70 | } 71 | 72 | function validateParameters (file, params) { 73 | const valid = params != null 74 | && typeof params.url === 'string' 75 | && (typeof params.fields === 'object' || params.fields == null) 76 | 77 | if (!valid) { 78 | const err = new TypeError(`AwsS3: got incorrect result from 'getUploadParameters()' for file '${file.name}', expected an object '{ url, method, fields, headers }' but got '${JSON.stringify(params)}' instead.\nSee https://uppy.io/docs/aws-s3/#getUploadParameters-file for more on the expected format.`) 79 | throw err 80 | } 81 | 82 | const methodIsValid = params.method == null || /^p(u|os)t$/i.test(params.method) 83 | 84 | if (!methodIsValid) { 85 | const err = new TypeError(`AwsS3: got incorrect method from 'getUploadParameters()' for file '${file.name}', expected 'PUT' or 'POST' but got '${params.method}' instead.\nSee https://uppy.io/docs/aws-s3/#getUploadParameters-file for more on the expected format.`) 86 | throw err 87 | } 88 | } 89 | 90 | // Get the error data from a failed XMLHttpRequest instance. 91 | // `content` is the S3 response as a string. 92 | // `xhr` is the XMLHttpRequest instance. 93 | function defaultGetResponseError (content, xhr) { 94 | // If no response, we don't have a specific error message, use the default. 95 | if (!isXml(content, xhr)) { 96 | return undefined 97 | } 98 | const error = getXmlValue(content, 'Message') 99 | return new Error(error) 100 | } 101 | 102 | // warning deduplication flag: see `getResponseData()` XHRUpload option definition 103 | let warnedSuccessActionStatus = false 104 | 105 | // TODO deprecate this, will use s3-multipart instead 106 | export default class AwsS3 extends BasePlugin { 107 | static VERSION = packageJson.version 108 | 109 | #client 110 | 111 | #requests 112 | 113 | #uploader 114 | 115 | constructor (uppy, opts) { 116 | // Opt-in to using the multipart plugin, which is going to be the only S3 plugin as of the next semver. 117 | if (opts?.shouldUseMultipart != null) { 118 | return new AwsS3Multipart(uppy, opts) 119 | } 120 | super(uppy, opts) 121 | this.type = 'uploader' 122 | this.id = this.opts.id || 'AwsS3' 123 | this.title = 'AWS S3' 124 | 125 | this.defaultLocale = locale 126 | 127 | const defaultOptions = { 128 | timeout: 30 * 1000, 129 | limit: 0, 130 | allowedMetaFields: [], // have to opt in 131 | getUploadParameters: this.getUploadParameters.bind(this), 132 | shouldUseMultipart: false, 133 | companionHeaders: {}, 134 | } 135 | 136 | this.opts = { ...defaultOptions, ...opts } 137 | 138 | if (opts?.allowedMetaFields === undefined && 'metaFields' in this.opts) { 139 | throw new Error('The `metaFields` option has been renamed to `allowedMetaFields`.') 140 | } 141 | 142 | // TODO: remove i18n once we can depend on XHRUpload instead of MiniXHRUpload 143 | this.i18nInit() 144 | 145 | this.#client = new RequestClient(uppy, opts) 146 | this.#requests = new RateLimitedQueue(this.opts.limit) 147 | } 148 | 149 | [Symbol.for('uppy test: getClient')] () { return this.#client } 150 | 151 | // TODO: remove getter and setter for #client on the next major release 152 | get client () { return this.#client } 153 | 154 | set client (client) { this.#client = client } 155 | 156 | getUploadParameters (file) { 157 | if (!this.opts.companionUrl) { 158 | throw new Error('Expected a `companionUrl` option containing a Companion address.') 159 | } 160 | 161 | const filename = file.meta.name 162 | const { type } = file.meta 163 | const metadata = Object.fromEntries( 164 | this.opts.allowedMetaFields 165 | .filter(key => file.meta[key] != null) 166 | .map(key => [`metadata[${key}]`, file.meta[key].toString()]), 167 | ) 168 | 169 | const query = new URLSearchParams({ filename, type, ...metadata }) 170 | return this.#client.get(`s3/params?${query}`) 171 | .then(assertServerError) 172 | } 173 | 174 | #handleUpload = async (fileIDs) => { 175 | /** 176 | * keep track of `getUploadParameters()` responses 177 | * so we can cancel the calls individually using just a file ID 178 | * 179 | * @type {Record>} 180 | */ 181 | const paramsPromises = Object.create(null) 182 | 183 | function onremove (file) { 184 | const { id } = file 185 | paramsPromises[id]?.abort() 186 | } 187 | this.uppy.on('file-removed', onremove) 188 | 189 | const files = this.uppy.getFilesByIds(fileIDs) 190 | 191 | const filesFiltered = filterNonFailedFiles(files) 192 | const filesToEmit = filterFilesToEmitUploadStarted(filesFiltered) 193 | this.uppy.emit('upload-start', filesToEmit) 194 | 195 | const getUploadParameters = this.#requests.wrapPromiseFunction((file) => { 196 | return this.opts.getUploadParameters(file) 197 | }) 198 | 199 | const numberOfFiles = fileIDs.length 200 | 201 | return Promise.allSettled(fileIDs.map((id, index) => { 202 | paramsPromises[id] = getUploadParameters(this.uppy.getFile(id)) 203 | return paramsPromises[id].then((params) => { 204 | delete paramsPromises[id] 205 | 206 | const file = this.uppy.getFile(id) 207 | validateParameters(file, params) 208 | 209 | const { 210 | method = 'POST', 211 | url, 212 | fields, 213 | headers, 214 | } = params 215 | const xhrOpts = { 216 | method, 217 | formData: method.toUpperCase() === 'POST', 218 | endpoint: url, 219 | allowedMetaFields: fields ? Object.keys(fields) : [], 220 | } 221 | 222 | if (headers) { 223 | xhrOpts.headers = headers 224 | } 225 | 226 | this.uppy.setFileState(file.id, { 227 | meta: { ...file.meta, ...fields }, 228 | xhrUpload: xhrOpts, 229 | }) 230 | 231 | return this.uploadFile(file.id, index, numberOfFiles) 232 | }).catch((error) => { 233 | delete paramsPromises[id] 234 | 235 | const file = this.uppy.getFile(id) 236 | this.uppy.emit('upload-error', file, error) 237 | return Promise.reject(error) 238 | }) 239 | })).finally(() => { 240 | // cleanup. 241 | this.uppy.off('file-removed', onremove) 242 | }) 243 | } 244 | 245 | #setCompanionHeaders = () => { 246 | this.#client.setCompanionHeaders(this.opts.companionHeaders) 247 | return Promise.resolve() 248 | } 249 | 250 | #getCompanionClientArgs = (file) => { 251 | const opts = this.#uploader.getOptions(file) 252 | const allowedMetaFields = Array.isArray(opts.allowedMetaFields) 253 | ? opts.allowedMetaFields 254 | // Send along all fields by default. 255 | : Object.keys(file.meta) 256 | return { 257 | ...file.remote.body, 258 | protocol: 'multipart', 259 | endpoint: opts.endpoint, 260 | size: file.data.size, 261 | fieldname: opts.fieldName, 262 | metadata: Object.fromEntries(allowedMetaFields.map(name => [name, file.meta[name]])), 263 | httpMethod: opts.method, 264 | useFormData: opts.formData, 265 | headers: typeof opts.headers === 'function' ? opts.headers(file) : opts.headers, 266 | } 267 | } 268 | 269 | uploadFile (id, current, total) { 270 | const file = this.uppy.getFile(id) 271 | this.uppy.log(`uploading ${current} of ${total}`) 272 | 273 | if (file.error) throw new Error(file.error) 274 | 275 | if (file.isRemote) { 276 | const getQueue = () => this.#requests 277 | const controller = new AbortController() 278 | 279 | const removedHandler = (removedFile) => { 280 | if (removedFile.id === file.id) controller.abort() 281 | } 282 | this.uppy.on('file-removed', removedHandler) 283 | 284 | const uploadPromise = file.remote.requestClient.uploadRemoteFile( 285 | file, 286 | this.#getCompanionClientArgs(file), 287 | { signal: controller.signal, getQueue }, 288 | ) 289 | 290 | this.#requests.wrapSyncFunction(() => { 291 | this.uppy.off('file-removed', removedHandler) 292 | }, { priority: -1 })() 293 | 294 | return uploadPromise 295 | } 296 | 297 | return this.#uploader.uploadLocalFile(file, current, total) 298 | } 299 | 300 | install () { 301 | const { uppy } = this 302 | uppy.addPreProcessor(this.#setCompanionHeaders) 303 | uppy.addUploader(this.#handleUpload) 304 | 305 | // Get the response data from a successful XMLHttpRequest instance. 306 | // `content` is the S3 response as a string. 307 | // `xhr` is the XMLHttpRequest instance. 308 | function defaultGetResponseData (content, xhr) { 309 | const opts = this 310 | 311 | // If no response, we've hopefully done a PUT request to the file 312 | // in the bucket on its full URL. 313 | if (!isXml(content, xhr)) { 314 | if (opts.method.toUpperCase() === 'POST') { 315 | if (!warnedSuccessActionStatus) { 316 | uppy.log('[AwsS3] No response data found, make sure to set the success_action_status AWS SDK option to 201. See https://uppy.io/docs/aws-s3/#POST-Uploads', 'warning') 317 | warnedSuccessActionStatus = true 318 | } 319 | // The responseURL won't contain the object key. Give up. 320 | return { location: null } 321 | } 322 | 323 | // responseURL is not available in older browsers. 324 | if (!xhr.responseURL) { 325 | return { location: null } 326 | } 327 | 328 | // Trim the query string because it's going to be a bunch of presign 329 | // parameters for a PUT request—doing a GET request with those will 330 | // always result in an error 331 | return { location: xhr.responseURL.replace(/\?.*$/, '') } 332 | } 333 | 334 | return { 335 | // Some S3 alternatives do not reply with an absolute URL. 336 | // Eg DigitalOcean Spaces uses /$bucketName/xyz 337 | location: resolveUrl(xhr.responseURL, getXmlValue(content, 'Location')), 338 | bucket: getXmlValue(content, 'Bucket'), 339 | key: getXmlValue(content, 'Key'), 340 | etag: getXmlValue(content, 'ETag'), 341 | } 342 | } 343 | 344 | const xhrOptions = { 345 | fieldName: 'file', 346 | responseUrlFieldName: 'location', 347 | timeout: this.opts.timeout, 348 | // Share the rate limiting queue with XHRUpload. 349 | [internalRateLimitedQueue]: this.#requests, 350 | responseType: 'text', 351 | getResponseData: this.opts.getResponseData || defaultGetResponseData, 352 | getResponseError: defaultGetResponseError, 353 | } 354 | 355 | // TODO: remove i18n once we can depend on XHRUpload instead of MiniXHRUpload 356 | xhrOptions.i18n = this.i18n 357 | 358 | // Revert to `uppy.use(XHRUpload)` once the big comment block at the top of 359 | // this file is solved 360 | this.#uploader = new MiniXHRUpload(uppy, xhrOptions) 361 | } 362 | 363 | uninstall () { 364 | this.uppy.removePreProcessor(this.#setCompanionHeaders) 365 | this.uppy.removeUploader(this.#handleUpload) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /packages/@ImageTestVarLang/companion-client/src/RequestClient.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import UserFacingApiError from '@uppy/utils/lib/UserFacingApiError' 4 | // eslint-disable-next-line import/no-extraneous-dependencies 5 | import pRetry, { AbortError } from 'p-retry' 6 | 7 | import fetchWithNetworkError from '@uppy/utils/lib/fetchWithNetworkError' 8 | import ErrorWithCause from '@uppy/utils/lib/ErrorWithCause' 9 | import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress' 10 | import getSocketHost from '@uppy/utils/lib/getSocketHost' 11 | 12 | import AuthError from './AuthError.js' 13 | 14 | import packageJson from '../package.json' 15 | 16 | // Remove the trailing slash so we can always safely append /xyz. 17 | function stripSlash(url) { 18 | return url.replace(/\/$/, '') 19 | } 20 | 21 | const retryCount = 10 // set to a low number, like 2 to test manual user retries 22 | const socketActivityTimeoutMs = 5 * 60 * 1000 // set to a low number like 10000 to test this 23 | 24 | export const authErrorStatusCode = 401 25 | 26 | class HttpError extends Error { 27 | statusCode 28 | 29 | constructor({ statusCode, message }) { 30 | super(message) 31 | this.name = 'HttpError' 32 | this.statusCode = statusCode 33 | } 34 | } 35 | 36 | async function handleJSONResponse(res) { 37 | if (res.status === authErrorStatusCode) { 38 | throw new AuthError() 39 | } 40 | 41 | if (res.ok) { 42 | return res.json() 43 | } 44 | 45 | let errMsg = `Failed request with status: ${res.status}. ${res.statusText}` 46 | let errData 47 | try { 48 | errData = await res.json() 49 | 50 | if (errData.message) errMsg = `${errMsg} message: ${errData.message}` 51 | if (errData.requestId) errMsg = `${errMsg} request-Id: ${errData.requestId}` 52 | } catch (cause) { 53 | // if the response contains invalid JSON, let's ignore the error data 54 | throw new Error(errMsg, { cause }) 55 | } 56 | 57 | if (res.status >= 400 && res.status <= 499 && errData.message) { 58 | throw new UserFacingApiError(errData.message) 59 | } 60 | 61 | throw new HttpError({ statusCode: res.status, message: errMsg }) 62 | } 63 | 64 | export default class RequestClient { 65 | static VERSION = packageJson.version 66 | 67 | #companionHeaders 68 | 69 | constructor(uppy, opts) { 70 | this.uppy = uppy 71 | this.opts = opts 72 | this.onReceiveResponse = this.onReceiveResponse.bind(this) 73 | this.#companionHeaders = opts?.companionHeaders 74 | } 75 | 76 | setCompanionHeaders(headers) { 77 | this.#companionHeaders = headers 78 | } 79 | 80 | [Symbol.for('uppy test: getCompanionHeaders')]() { 81 | return this.#companionHeaders 82 | } 83 | 84 | get hostname() { 85 | const { companion } = this.uppy.getState() 86 | const host = this.opts.companionUrl 87 | return stripSlash(companion && companion[host] ? companion[host] : host) 88 | } 89 | 90 | async headers (emptyBody = false) { 91 | const defaultHeaders = { 92 | Accept: 'application/json', 93 | ...(emptyBody ? undefined : { 94 | // Passing those headers on requests with no data forces browsers to first make a preflight request. 95 | 'Content-Type': 'application/json', 96 | }), 97 | } 98 | 99 | return { 100 | ...defaultHeaders, 101 | ...this.#companionHeaders, 102 | } 103 | } 104 | 105 | onReceiveResponse({ headers }) { 106 | const state = this.uppy.getState() 107 | const companion = state.companion || {} 108 | const host = this.opts.companionUrl 109 | 110 | // Store the self-identified domain name for the Companion instance we just hit. 111 | if (headers.has('i-am') && headers.get('i-am') !== companion[host]) { 112 | this.uppy.setState({ 113 | companion: { ...companion, [host]: headers.get('i-am') }, 114 | }) 115 | } 116 | } 117 | 118 | #getUrl(url) { 119 | if (/^(https?:|)\/\//.test(url)) { 120 | return url 121 | } 122 | return `${this.hostname}/${url}` 123 | } 124 | 125 | /** @protected */ 126 | async request({ path, method = 'GET', data, skipPostResponse, signal }) { 127 | try { 128 | const headers = await this.headers(!data) 129 | const response = await fetchWithNetworkError(this.#getUrl(path), { 130 | method, 131 | signal, 132 | headers, 133 | credentials: this.opts.companionCookiesRule || 'same-origin', 134 | body: data ? JSON.stringify(data) : null, 135 | }) 136 | if (!skipPostResponse) this.onReceiveResponse(response) 137 | 138 | return await handleJSONResponse(response) 139 | } catch (err) { 140 | // pass these through 141 | if (err.isAuthError || err.name === 'UserFacingApiError' || err.name === 'AbortError') throw err 142 | 143 | throw new ErrorWithCause(`Could not ${method} ${this.#getUrl(path)}`, { 144 | cause: err, 145 | }) 146 | } 147 | } 148 | 149 | async get(path, options = undefined) { 150 | // TODO: remove boolean support for options that was added for backward compatibility. 151 | // eslint-disable-next-line no-param-reassign 152 | if (typeof options === 'boolean') options = { skipPostResponse: options } 153 | return this.request({ ...options, path }) 154 | } 155 | 156 | async post(path, data, options = undefined) { 157 | // TODO: remove boolean support for options that was added for backward compatibility. 158 | // eslint-disable-next-line no-param-reassign 159 | if (typeof options === 'boolean') options = { skipPostResponse: options } 160 | return this.request({ ...options, path, method: 'POST', data }) 161 | } 162 | 163 | async delete(path, data = undefined, options) { 164 | // TODO: remove boolean support for options that was added for backward compatibility. 165 | // eslint-disable-next-line no-param-reassign 166 | if (typeof options === 'boolean') options = { skipPostResponse: options } 167 | return this.request({ ...options, path, method: 'DELETE', data }) 168 | } 169 | 170 | /** 171 | * Remote uploading consists of two steps: 172 | * 1. #requestSocketToken which starts the download/upload in companion and returns a unique token for the upload. 173 | * Then companion will halt the upload until: 174 | * 2. #awaitRemoteFileUpload is called, which will open/ensure a websocket connection towards companion, with the 175 | * previously generated token provided. It returns a promise that will resolve/reject once the file has finished 176 | * uploading or is otherwise done (failed, canceled) 177 | * 178 | * @param {*} file 179 | * @param {*} reqBody 180 | * @param {*} options 181 | * @returns 182 | */ 183 | async uploadRemoteFile(file, reqBody, options = {}) { 184 | try { 185 | const { signal, getQueue } = options 186 | 187 | return await pRetry(async () => { 188 | // if we already have a serverToken, assume that we are resuming the existing server upload id 189 | const existingServerToken = this.uppy.getFile(file.id)?.serverToken; 190 | if (existingServerToken != null) { 191 | this.uppy.log(`Connecting to exiting websocket ${existingServerToken}`) 192 | return this.#awaitRemoteFileUpload({ file, queue: getQueue(), signal }) 193 | } 194 | 195 | const queueRequestSocketToken = getQueue().wrapPromiseFunction(async (...args) => { 196 | try { 197 | return await this.#requestSocketToken(...args) 198 | } catch (outerErr) { 199 | // throwing AbortError will cause p-retry to stop retrying 200 | if (outerErr.isAuthError) throw new AbortError(outerErr) 201 | 202 | if (outerErr.cause == null) throw outerErr 203 | const err = outerErr.cause 204 | 205 | const isRetryableHttpError = () => ( 206 | [408, 409, 429, 418, 423].includes(err.statusCode) 207 | || (err.statusCode >= 500 && err.statusCode <= 599 && ![501, 505].includes(err.statusCode)) 208 | ) 209 | if (err.name === 'HttpError' && !isRetryableHttpError()) throw new AbortError(err); 210 | 211 | // p-retry will retry most other errors, 212 | // but it will not retry TypeError (except network error TypeErrors) 213 | throw err 214 | } 215 | }, { priority: -1 }) 216 | 217 | const serverToken = await queueRequestSocketToken({ file, postBody: reqBody, signal }).abortOn(signal) 218 | 219 | if (!this.uppy.getFile(file.id)) return undefined // has file since been removed? 220 | 221 | this.uppy.setFileState(file.id, { serverToken }) 222 | 223 | return this.#awaitRemoteFileUpload({ 224 | file: this.uppy.getFile(file.id), // re-fetching file because it might have changed in the meantime 225 | queue: getQueue(), 226 | signal 227 | }) 228 | }, { retries: retryCount, signal, onFailedAttempt: (err) => this.uppy.log(`Retrying upload due to: ${err.message}`, 'warning') }); 229 | } catch (err) { 230 | // this is a bit confusing, but note that an error with the `name` prop set to 'AbortError' (from AbortController) 231 | // is not the same as `p-retry` `AbortError` 232 | if (err.name === 'AbortError') { 233 | // The file upload was aborted, it’s not an error 234 | return undefined 235 | } 236 | 237 | this.uppy.emit('upload-error', file, err) 238 | throw err 239 | } 240 | } 241 | 242 | #requestSocketToken = async ({ file, postBody, signal }) => { 243 | if (file.remote.url == null) { 244 | throw new Error('Cannot connect to an undefined URL') 245 | } 246 | 247 | const res = await this.post(file.remote.url, { 248 | ...file.remote.body, 249 | ...postBody, 250 | }, signal) 251 | 252 | return res.token 253 | } 254 | 255 | /** 256 | * This method will ensure a websocket for the specified file and returns a promise that resolves 257 | * when the file has finished downloading, or rejects if it fails. 258 | * It will retry if the websocket gets disconnected 259 | * 260 | * @param {{ file: UppyFile, queue: RateLimitedQueue, signal: AbortSignal }} file 261 | */ 262 | async #awaitRemoteFileUpload({ file, queue, signal }) { 263 | let removeEventHandlers 264 | 265 | const { capabilities } = this.uppy.getState() 266 | 267 | try { 268 | return await new Promise((resolve, reject) => { 269 | const token = file.serverToken 270 | const host = getSocketHost(file.remote.companionUrl) 271 | 272 | /** @type {WebSocket} */ 273 | let socket 274 | /** @type {AbortController?} */ 275 | let socketAbortController 276 | let activityTimeout 277 | 278 | let { isPaused } = file 279 | 280 | const socketSend = (action, payload) => { 281 | if (socket == null || socket.readyState !== socket.OPEN) { 282 | this.uppy.log(`Cannot send "${action}" to socket ${file.id} because the socket state was ${String(socket?.readyState)}`, 'warning') 283 | return 284 | } 285 | 286 | socket.send(JSON.stringify({ 287 | action, 288 | payload: payload ?? {}, 289 | })) 290 | }; 291 | 292 | function sendState() { 293 | if (!capabilities.resumableUploads) return; 294 | 295 | if (isPaused) socketSend('pause') 296 | else socketSend('resume') 297 | } 298 | 299 | const createWebsocket = async () => { 300 | if (socketAbortController) socketAbortController.abort() 301 | socketAbortController = new AbortController() 302 | 303 | const onFatalError = (err) => { 304 | // Remove the serverToken so that a new one will be created for the retry. 305 | this.uppy.setFileState(file.id, { serverToken: null }) 306 | socketAbortController?.abort?.() 307 | reject(err) 308 | } 309 | 310 | // todo instead implement the ability for users to cancel / retry *currently uploading files* in the UI 311 | function resetActivityTimeout() { 312 | clearTimeout(activityTimeout) 313 | if (isPaused) return 314 | activityTimeout = setTimeout(() => onFatalError(new Error('Timeout waiting for message from Companion socket')), socketActivityTimeoutMs) 315 | } 316 | 317 | try { 318 | await queue.wrapPromiseFunction(async () => { 319 | // eslint-disable-next-line promise/param-names 320 | const reconnectWebsocket = async () => new Promise((resolveSocket, rejectSocket) => { 321 | socket = new WebSocket(`${host}/api/${token}`) 322 | 323 | resetActivityTimeout() 324 | 325 | socket.addEventListener('close', () => { 326 | socket = undefined 327 | rejectSocket(new Error('Socket closed unexpectedly')) 328 | }) 329 | 330 | socket.addEventListener('error', (error) => { 331 | this.uppy.log(`Companion socket error ${JSON.stringify(error)}, closing socket`, 'warning') 332 | socket.close() // will 'close' event to be emitted 333 | }) 334 | 335 | socket.addEventListener('open', () => { 336 | sendState() 337 | }) 338 | 339 | socket.addEventListener('message', (e) => { 340 | resetActivityTimeout() 341 | 342 | try { 343 | const { action, payload } = JSON.parse(e.data) 344 | 345 | switch (action) { 346 | case 'progress': { 347 | emitSocketProgress(this, payload, file) 348 | break; 349 | } 350 | case 'success': { 351 | this.uppy.emit('upload-success', file, { uploadURL: payload.url }) 352 | socketAbortController?.abort?.() 353 | resolve() 354 | break; 355 | } 356 | case 'error': { 357 | const { message } = payload.error 358 | throw Object.assign(new Error(message), { cause: payload.error }) 359 | } 360 | default: 361 | this.uppy.log(`Companion socket unknown action ${action}`, 'warning') 362 | } 363 | } catch (err) { 364 | onFatalError(err) 365 | } 366 | }) 367 | 368 | const closeSocket = () => { 369 | this.uppy.log(`Closing socket ${file.id}`, 'info') 370 | clearTimeout(activityTimeout) 371 | if (socket) socket.close() 372 | socket = undefined 373 | } 374 | 375 | socketAbortController.signal.addEventListener('abort', () => { 376 | closeSocket() 377 | }) 378 | }) 379 | 380 | await pRetry(reconnectWebsocket, { 381 | retries: retryCount, 382 | signal: socketAbortController.signal, 383 | onFailedAttempt: () => { 384 | if (socketAbortController.signal.aborted) return // don't log in this case 385 | this.uppy.log(`Retrying websocket ${file.id}`, 'info') 386 | }, 387 | }); 388 | })().abortOn(socketAbortController.signal); 389 | } catch (err) { 390 | if (socketAbortController.signal.aborted) return 391 | onFatalError(err) 392 | } 393 | } 394 | 395 | const pause = (newPausedState) => { 396 | if (!capabilities.resumableUploads) return; 397 | 398 | isPaused = newPausedState 399 | if (socket) sendState() 400 | 401 | if (newPausedState) { 402 | // Remove this file from the queue so another file can start in its place. 403 | socketAbortController?.abort?.() // close socket to free up the request for other uploads 404 | } else { 405 | // Resuming an upload should be queued, else you could pause and then 406 | // resume a queued upload to make it skip the queue. 407 | createWebsocket() 408 | } 409 | } 410 | 411 | const onFileRemove = (targetFile) => { 412 | if (!capabilities.individualCancellation) return 413 | if (targetFile.id !== file.id) return 414 | socketSend('cancel') 415 | socketAbortController?.abort?.() 416 | this.uppy.log(`upload ${file.id} was removed`, 'info') 417 | resolve() 418 | } 419 | 420 | const onCancelAll = ({ reason }) => { 421 | if (reason === 'user') { 422 | socketSend('cancel') 423 | } 424 | socketAbortController?.abort?.() 425 | this.uppy.log(`upload ${file.id} was canceled`, 'info') 426 | resolve() 427 | }; 428 | 429 | const onFilePausedChange = (targetFileId, newPausedState) => { 430 | if (targetFileId !== file.id) return 431 | pause(newPausedState) 432 | } 433 | 434 | const onPauseAll = () => pause(true) 435 | const onResumeAll = () => pause(false) 436 | 437 | this.uppy.on('file-removed', onFileRemove) 438 | this.uppy.on('cancel-all', onCancelAll) 439 | this.uppy.on('upload-pause', onFilePausedChange) 440 | this.uppy.on('pause-all', onPauseAll) 441 | this.uppy.on('resume-all', onResumeAll) 442 | 443 | removeEventHandlers = () => { 444 | this.uppy.off('file-removed', onFileRemove) 445 | this.uppy.off('cancel-all', onCancelAll) 446 | this.uppy.off('upload-pause', onFilePausedChange) 447 | this.uppy.off('pause-all', onPauseAll) 448 | this.uppy.off('resume-all', onResumeAll) 449 | } 450 | 451 | signal.addEventListener('abort', () => { 452 | socketAbortController?.abort(); 453 | }) 454 | 455 | createWebsocket() 456 | }) 457 | } finally { 458 | removeEventHandlers?.() 459 | } 460 | } 461 | } 462 | --------------------------------------------------------------------------------