This commit is contained in:
parent
fa2be029a2
commit
724d70e58b
3339 changed files with 1075535 additions and 0 deletions
51
frontend/node_modules/tus-js-client/lib/browser/fileReader.js
generated
vendored
Normal file
51
frontend/node_modules/tus-js-client/lib/browser/fileReader.js
generated
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import isReactNative from './isReactNative.js'
|
||||
import uriToBlob from './uriToBlob.js'
|
||||
|
||||
import FileSource from './sources/FileSource.js'
|
||||
import StreamSource from './sources/StreamSource.js'
|
||||
|
||||
export default class FileReader {
|
||||
async openFile(input, chunkSize) {
|
||||
// In React Native, when user selects a file, instead of a File or Blob,
|
||||
// you usually get a file object {} with a uri property that contains
|
||||
// a local path to the file. We use XMLHttpRequest to fetch
|
||||
// the file blob, before uploading with tus.
|
||||
if (isReactNative() && input && typeof input.uri !== 'undefined') {
|
||||
try {
|
||||
const blob = await uriToBlob(input.uri)
|
||||
return new FileSource(blob)
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`tus: cannot fetch \`file.uri\` as Blob, make sure the uri is correct and accessible. ${err}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Since we emulate the Blob type in our tests (not all target browsers
|
||||
// support it), we cannot use `instanceof` for testing whether the input value
|
||||
// can be handled. Instead, we simply check is the slice() function and the
|
||||
// size property are available.
|
||||
if (typeof input.slice === 'function' && typeof input.size !== 'undefined') {
|
||||
return Promise.resolve(new FileSource(input))
|
||||
}
|
||||
|
||||
if (typeof input.read === 'function') {
|
||||
chunkSize = Number(chunkSize)
|
||||
if (!Number.isFinite(chunkSize)) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'cannot create source for stream without a finite value for the `chunkSize` option',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return Promise.resolve(new StreamSource(input, chunkSize))
|
||||
}
|
||||
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'source object may only be an instance of File, Blob, or Reader in this environment',
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
41
frontend/node_modules/tus-js-client/lib/browser/fileSignature.js
generated
vendored
Normal file
41
frontend/node_modules/tus-js-client/lib/browser/fileSignature.js
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import isReactNative from './isReactNative.js'
|
||||
|
||||
// TODO: Differenciate between input types
|
||||
|
||||
/**
|
||||
* Generate a fingerprint for a file which will be used the store the endpoint
|
||||
*
|
||||
* @param {File} file
|
||||
* @param {Object} options
|
||||
* @param {Function} callback
|
||||
*/
|
||||
export default function fingerprint(file, options) {
|
||||
if (isReactNative()) {
|
||||
return Promise.resolve(reactNativeFingerprint(file, options))
|
||||
}
|
||||
|
||||
return Promise.resolve(
|
||||
['tus-br', file.name, file.type, file.size, file.lastModified, options.endpoint].join('-'),
|
||||
)
|
||||
}
|
||||
|
||||
function reactNativeFingerprint(file, options) {
|
||||
const exifHash = file.exif ? hashCode(JSON.stringify(file.exif)) : 'noexif'
|
||||
return ['tus-rn', file.name || 'noname', file.size || 'nosize', exifHash, options.endpoint].join(
|
||||
'/',
|
||||
)
|
||||
}
|
||||
|
||||
function hashCode(str) {
|
||||
// from https://stackoverflow.com/a/8831937/151666
|
||||
let hash = 0
|
||||
if (str.length === 0) {
|
||||
return hash
|
||||
}
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i)
|
||||
hash = (hash << 5) - hash + char
|
||||
hash &= hash // Convert to 32bit integer
|
||||
}
|
||||
return hash
|
||||
}
|
||||
97
frontend/node_modules/tus-js-client/lib/browser/httpStack.js
generated
vendored
Normal file
97
frontend/node_modules/tus-js-client/lib/browser/httpStack.js
generated
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
export default class XHRHttpStack {
|
||||
createRequest(method, url) {
|
||||
return new Request(method, url)
|
||||
}
|
||||
|
||||
getName() {
|
||||
return 'XHRHttpStack'
|
||||
}
|
||||
}
|
||||
|
||||
class Request {
|
||||
constructor(method, url) {
|
||||
this._xhr = new XMLHttpRequest()
|
||||
this._xhr.open(method, url, true)
|
||||
|
||||
this._method = method
|
||||
this._url = url
|
||||
this._headers = {}
|
||||
}
|
||||
|
||||
getMethod() {
|
||||
return this._method
|
||||
}
|
||||
|
||||
getURL() {
|
||||
return this._url
|
||||
}
|
||||
|
||||
setHeader(header, value) {
|
||||
this._xhr.setRequestHeader(header, value)
|
||||
this._headers[header] = value
|
||||
}
|
||||
|
||||
getHeader(header) {
|
||||
return this._headers[header]
|
||||
}
|
||||
|
||||
setProgressHandler(progressHandler) {
|
||||
// Test support for progress events before attaching an event listener
|
||||
if (!('upload' in this._xhr)) {
|
||||
return
|
||||
}
|
||||
|
||||
this._xhr.upload.onprogress = (e) => {
|
||||
if (!e.lengthComputable) {
|
||||
return
|
||||
}
|
||||
|
||||
progressHandler(e.loaded)
|
||||
}
|
||||
}
|
||||
|
||||
send(body = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._xhr.onload = () => {
|
||||
resolve(new Response(this._xhr))
|
||||
}
|
||||
|
||||
this._xhr.onerror = (err) => {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
this._xhr.send(body)
|
||||
})
|
||||
}
|
||||
|
||||
abort() {
|
||||
this._xhr.abort()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
getUnderlyingObject() {
|
||||
return this._xhr
|
||||
}
|
||||
}
|
||||
|
||||
class Response {
|
||||
constructor(xhr) {
|
||||
this._xhr = xhr
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return this._xhr.status
|
||||
}
|
||||
|
||||
getHeader(header) {
|
||||
return this._xhr.getResponseHeader(header)
|
||||
}
|
||||
|
||||
getBody() {
|
||||
return this._xhr.responseText
|
||||
}
|
||||
|
||||
getUnderlyingObject() {
|
||||
return this._xhr
|
||||
}
|
||||
}
|
||||
45
frontend/node_modules/tus-js-client/lib/browser/index.js
generated
vendored
Normal file
45
frontend/node_modules/tus-js-client/lib/browser/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import DetailedError from '../error.js'
|
||||
import { enableDebugLog } from '../logger.js'
|
||||
import NoopUrlStorage from '../noopUrlStorage.js'
|
||||
import BaseUpload from '../upload.js'
|
||||
|
||||
import FileReader from './fileReader.js'
|
||||
import fingerprint from './fileSignature.js'
|
||||
import DefaultHttpStack from './httpStack.js'
|
||||
import { WebStorageUrlStorage, canStoreURLs } from './urlStorage.js'
|
||||
|
||||
const defaultOptions = {
|
||||
...BaseUpload.defaultOptions,
|
||||
httpStack: new DefaultHttpStack(),
|
||||
fileReader: new FileReader(),
|
||||
urlStorage: canStoreURLs ? new WebStorageUrlStorage() : new NoopUrlStorage(),
|
||||
fingerprint,
|
||||
}
|
||||
|
||||
class Upload extends BaseUpload {
|
||||
constructor(file = null, options = {}) {
|
||||
options = { ...defaultOptions, ...options }
|
||||
super(file, options)
|
||||
}
|
||||
|
||||
static terminate(url, options = {}) {
|
||||
options = { ...defaultOptions, ...options }
|
||||
return BaseUpload.terminate(url, options)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We don't reference `window` here because these classes also exist in a Web Worker's context.
|
||||
const isSupported =
|
||||
typeof XMLHttpRequest === 'function' &&
|
||||
typeof Blob === 'function' &&
|
||||
typeof Blob.prototype.slice === 'function'
|
||||
|
||||
export {
|
||||
Upload,
|
||||
canStoreURLs,
|
||||
defaultOptions,
|
||||
isSupported,
|
||||
enableDebugLog,
|
||||
DefaultHttpStack,
|
||||
DetailedError,
|
||||
}
|
||||
6
frontend/node_modules/tus-js-client/lib/browser/isReactNative.js
generated
vendored
Normal file
6
frontend/node_modules/tus-js-client/lib/browser/isReactNative.js
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const isReactNative = () =>
|
||||
typeof navigator !== 'undefined' &&
|
||||
typeof navigator.product === 'string' &&
|
||||
navigator.product.toLowerCase() === 'reactnative'
|
||||
|
||||
export default isReactNative
|
||||
27
frontend/node_modules/tus-js-client/lib/browser/sources/FileSource.js
generated
vendored
Normal file
27
frontend/node_modules/tus-js-client/lib/browser/sources/FileSource.js
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import isCordova from './isCordova.js'
|
||||
import readAsByteArray from './readAsByteArray.js'
|
||||
|
||||
export default class FileSource {
|
||||
// Make this.size a method
|
||||
constructor(file) {
|
||||
this._file = file
|
||||
this.size = file.size
|
||||
}
|
||||
|
||||
slice(start, end) {
|
||||
// In Apache Cordova applications, a File must be resolved using
|
||||
// FileReader instances, see
|
||||
// https://cordova.apache.org/docs/en/8.x/reference/cordova-plugin-file/index.html#read-a-file
|
||||
if (isCordova()) {
|
||||
return readAsByteArray(this._file.slice(start, end))
|
||||
}
|
||||
|
||||
const value = this._file.slice(start, end)
|
||||
const done = end >= this.size
|
||||
return Promise.resolve({ value, done })
|
||||
}
|
||||
|
||||
close() {
|
||||
// Nothing to do here since we don't need to release any resources.
|
||||
}
|
||||
}
|
||||
89
frontend/node_modules/tus-js-client/lib/browser/sources/StreamSource.js
generated
vendored
Normal file
89
frontend/node_modules/tus-js-client/lib/browser/sources/StreamSource.js
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
function len(blobOrArray) {
|
||||
if (blobOrArray === undefined) return 0
|
||||
if (blobOrArray.size !== undefined) return blobOrArray.size
|
||||
return blobOrArray.length
|
||||
}
|
||||
|
||||
/*
|
||||
Typed arrays and blobs don't have a concat method.
|
||||
This function helps StreamSource accumulate data to reach chunkSize.
|
||||
*/
|
||||
function concat(a, b) {
|
||||
if (a.concat) {
|
||||
// Is `a` an Array?
|
||||
return a.concat(b)
|
||||
}
|
||||
if (a instanceof Blob) {
|
||||
return new Blob([a, b], { type: a.type })
|
||||
}
|
||||
if (a.set) {
|
||||
// Is `a` a typed array?
|
||||
const c = new a.constructor(a.length + b.length)
|
||||
c.set(a)
|
||||
c.set(b, a.length)
|
||||
return c
|
||||
}
|
||||
throw new Error('Unknown data type')
|
||||
}
|
||||
|
||||
export default class StreamSource {
|
||||
constructor(reader) {
|
||||
this._buffer = undefined
|
||||
this._bufferOffset = 0
|
||||
this._reader = reader
|
||||
this._done = false
|
||||
}
|
||||
|
||||
slice(start, end) {
|
||||
if (start < this._bufferOffset) {
|
||||
return Promise.reject(new Error("Requested data is before the reader's current offset"))
|
||||
}
|
||||
|
||||
return this._readUntilEnoughDataOrDone(start, end)
|
||||
}
|
||||
|
||||
_readUntilEnoughDataOrDone(start, end) {
|
||||
const hasEnoughData = end <= this._bufferOffset + len(this._buffer)
|
||||
if (this._done || hasEnoughData) {
|
||||
const value = this._getDataFromBuffer(start, end)
|
||||
const done = value == null ? this._done : false
|
||||
return Promise.resolve({ value, done })
|
||||
}
|
||||
|
||||
return this._reader.read().then(({ value, done }) => {
|
||||
if (done) {
|
||||
this._done = true
|
||||
} else if (this._buffer === undefined) {
|
||||
this._buffer = value
|
||||
} else {
|
||||
this._buffer = concat(this._buffer, value)
|
||||
}
|
||||
|
||||
return this._readUntilEnoughDataOrDone(start, end)
|
||||
})
|
||||
}
|
||||
|
||||
_getDataFromBuffer(start, end) {
|
||||
// Remove data from buffer before `start`.
|
||||
// Data might be reread from the buffer if an upload fails, so we can only
|
||||
// safely delete data when it comes *before* what is currently being read.
|
||||
if (start > this._bufferOffset) {
|
||||
this._buffer = this._buffer.slice(start - this._bufferOffset)
|
||||
this._bufferOffset = start
|
||||
}
|
||||
// If the buffer is empty after removing old data, all data has been read.
|
||||
const hasAllDataBeenRead = len(this._buffer) === 0
|
||||
if (this._done && hasAllDataBeenRead) {
|
||||
return null
|
||||
}
|
||||
// We already removed data before `start`, so we just return the first
|
||||
// chunk from the buffer.
|
||||
return this._buffer.slice(0, end - start)
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this._reader.cancel) {
|
||||
this._reader.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
7
frontend/node_modules/tus-js-client/lib/browser/sources/isCordova.js
generated
vendored
Normal file
7
frontend/node_modules/tus-js-client/lib/browser/sources/isCordova.js
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const isCordova = () =>
|
||||
typeof window !== 'undefined' &&
|
||||
(typeof window.PhoneGap !== 'undefined' ||
|
||||
typeof window.Cordova !== 'undefined' ||
|
||||
typeof window.cordova !== 'undefined')
|
||||
|
||||
export default isCordova
|
||||
18
frontend/node_modules/tus-js-client/lib/browser/sources/readAsByteArray.js
generated
vendored
Normal file
18
frontend/node_modules/tus-js-client/lib/browser/sources/readAsByteArray.js
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* readAsByteArray converts a File object to a Uint8Array.
|
||||
* This function is only used on the Apache Cordova platform.
|
||||
* See https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-file/index.html#read-a-file
|
||||
*/
|
||||
export default function readAsByteArray(chunk) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
const value = new Uint8Array(reader.result)
|
||||
resolve({ value })
|
||||
}
|
||||
reader.onerror = (err) => {
|
||||
reject(err)
|
||||
}
|
||||
reader.readAsArrayBuffer(chunk)
|
||||
})
|
||||
}
|
||||
20
frontend/node_modules/tus-js-client/lib/browser/uriToBlob.js
generated
vendored
Normal file
20
frontend/node_modules/tus-js-client/lib/browser/uriToBlob.js
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* uriToBlob resolves a URI to a Blob object. This is used for
|
||||
* React Native to retrieve a file (identified by a file://
|
||||
* URI) as a blob.
|
||||
*/
|
||||
export default function uriToBlob(uri) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.responseType = 'blob'
|
||||
xhr.onload = () => {
|
||||
const blob = xhr.response
|
||||
resolve(blob)
|
||||
}
|
||||
xhr.onerror = (err) => {
|
||||
reject(err)
|
||||
}
|
||||
xhr.open('GET', uri)
|
||||
xhr.send()
|
||||
})
|
||||
}
|
||||
71
frontend/node_modules/tus-js-client/lib/browser/urlStorage.js
generated
vendored
Normal file
71
frontend/node_modules/tus-js-client/lib/browser/urlStorage.js
generated
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
let hasStorage = false
|
||||
try {
|
||||
// Note: localStorage does not exist in the Web Worker's context, so we must use window here.
|
||||
hasStorage = 'localStorage' in window
|
||||
|
||||
// Attempt to store and read entries from the local storage to detect Private
|
||||
// Mode on Safari on iOS (see #49)
|
||||
// If the key was not used before, we remove it from local storage again to
|
||||
// not cause confusion where the entry came from.
|
||||
const key = 'tusSupport'
|
||||
const originalValue = localStorage.getItem(key)
|
||||
localStorage.setItem(key, originalValue)
|
||||
if (originalValue === null) localStorage.removeItem(key)
|
||||
} catch (e) {
|
||||
// If we try to access localStorage inside a sandboxed iframe, a SecurityError
|
||||
// is thrown. When in private mode on iOS Safari, a QuotaExceededError is
|
||||
// thrown (see #49)
|
||||
if (e.code === e.SECURITY_ERR || e.code === e.QUOTA_EXCEEDED_ERR) {
|
||||
hasStorage = false
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export const canStoreURLs = hasStorage
|
||||
|
||||
export class WebStorageUrlStorage {
|
||||
findAllUploads() {
|
||||
const results = this._findEntries('tus::')
|
||||
return Promise.resolve(results)
|
||||
}
|
||||
|
||||
findUploadsByFingerprint(fingerprint) {
|
||||
const results = this._findEntries(`tus::${fingerprint}::`)
|
||||
return Promise.resolve(results)
|
||||
}
|
||||
|
||||
removeUpload(urlStorageKey) {
|
||||
localStorage.removeItem(urlStorageKey)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
addUpload(fingerprint, upload) {
|
||||
const id = Math.round(Math.random() * 1e12)
|
||||
const key = `tus::${fingerprint}::${id}`
|
||||
|
||||
localStorage.setItem(key, JSON.stringify(upload))
|
||||
return Promise.resolve(key)
|
||||
}
|
||||
|
||||
_findEntries(prefix) {
|
||||
const results = []
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i)
|
||||
if (key.indexOf(prefix) !== 0) continue
|
||||
|
||||
try {
|
||||
const upload = JSON.parse(localStorage.getItem(key))
|
||||
upload.urlStorageKey = key
|
||||
|
||||
results.push(upload)
|
||||
} catch (_e) {
|
||||
// The JSON parse error is intentionally ignored here, so a malformed
|
||||
// entry in the storage cannot prevent an upload.
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
}
|
||||
Reference in a new issue