UNPKG

ngx-uploadx

Version:
1 lines 74.8 kB
{"version":3,"file":"ngx-uploadx.mjs","sources":["../../../src/uploadx/lib/ajax.ts","../../../src/uploadx/lib/canceler.ts","../../../src/uploadx/lib/dynamic-chunk.ts","../../../src/uploadx/lib/utils.ts","../../../src/uploadx/lib/id.service.ts","../../../src/uploadx/lib/retry-handler.ts","../../../src/uploadx/lib/store.ts","../../../src/uploadx/lib/uploader.ts","../../../src/uploadx/lib/uploaderx.ts","../../../src/uploadx/lib/options.ts","../../../src/uploadx/lib/tus.ts","../../../src/uploadx/lib/uploadx.service.ts","../../../src/uploadx/lib/uploadx.directive.ts","../../../src/uploadx/lib/uploadx-drop.directive.ts","../../../src/uploadx/lib/uploadx.module.ts","../../../src/uploadx/lib/uploadx.provider.ts","../../../src/uploadx/ngx-uploadx.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { RequestOptions } from './interfaces';\n\nexport interface AjaxRequestConfig extends RequestOptions {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [x: string]: any;\n\n data?: BodyInit | null;\n url: string;\n}\n\nexport interface AjaxResponse<T> {\n data: T;\n status: number;\n headers: Record<string, string>;\n}\n\nexport interface Ajax {\n request: <T = string>(config: AjaxRequestConfig) => Promise<AjaxResponse<T>>;\n}\n\nfunction createXhr(): XMLHttpRequest {\n return new XMLHttpRequest();\n}\n\nfunction releaseXhr(_xhr: unknown): void {\n _xhr = null;\n}\n\nexport class UploadxAjax {\n constructor(private buildXhr: () => XMLHttpRequest) {}\n\n request = <T = string>({\n method = 'GET',\n data = null,\n headers = {},\n url,\n responseType,\n signal,\n onUploadProgress,\n timeout = 0,\n withCredentials = false,\n validateStatus = status => status < 400 && status >= 200\n }: AjaxRequestConfig): Promise<AjaxResponse<T>> => {\n return new Promise((resolve, reject) => {\n const xhr = this.buildXhr();\n const abortListener = () => xhr && xhr.readyState !== xhr.DONE && xhr.abort();\n signal?.addEventListener('abort', abortListener, { once: true });\n xhr.open(method, url, true);\n xhr.timeout = timeout;\n withCredentials && (xhr.withCredentials = true);\n if (responseType && responseType !== 'json') {\n xhr.responseType = responseType;\n }\n Object.keys(headers).forEach(key => xhr.setRequestHeader(key, String(headers[key])));\n xhr.upload.onprogress = onUploadProgress || null;\n xhr.onerror =\n xhr.ontimeout =\n xhr.onabort =\n evt => {\n releaseXhr(xhr);\n signal?.removeEventListener('abort', abortListener);\n return reject({ error: evt.type, url, method });\n };\n xhr.onload = () => {\n const response = {\n data: this.getResponseBody<T>(xhr, responseType),\n status: xhr.status,\n headers: this.getResponseHeaders(xhr)\n };\n releaseXhr(xhr);\n signal?.removeEventListener('abort', abortListener);\n return validateStatus(response.status) ? resolve(response) : reject(response);\n };\n xhr.send(data as XMLHttpRequestBodyInit);\n });\n };\n\n getResponseHeaders(xhr: XMLHttpRequest): Record<string, string> {\n const rows = xhr.getAllResponseHeaders().split(/[\\r\\n]+/);\n return rows.reduce((headers: Record<string, string>, current) => {\n const [name, value] = current.split(': ');\n name && (headers[name.toLowerCase()] = value);\n return headers;\n }, {});\n }\n\n getResponseBody<T>(xhr: XMLHttpRequest, responseType?: string): T {\n let body = typeof xhr.response === 'undefined' ? xhr.responseText : xhr.response;\n if (responseType === 'json' && body && typeof body === 'string') {\n try {\n body = JSON.parse(body);\n } catch {}\n }\n return body;\n }\n}\n\nexport const UPLOADX_AJAX: InjectionToken<Ajax> = new InjectionToken('uploadx.ajax', {\n factory: () => new UploadxAjax(createXhr),\n providedIn: 'root'\n});\n","/**\n * Allows canceling some operation by calling cancel().\n * onCancel callback can be used to execute cleanup logic when cancel is called.\n */\nexport class Canceler {\n /**\n * Callback function to execute cleanup logic when cancel() is called\n */\n onCancel = (): void => {};\n\n /**\n * Cancels the operation.\n */\n cancel(): void {\n this.onCancel();\n this.onCancel = () => {};\n }\n}\n","const KiB = 1024;\n/**\n * Adaptive chunk size\n */\nexport class DynamicChunk {\n /** Maximum chunk size in bytes */\n static maxSize = Number.MAX_SAFE_INTEGER;\n /** Minimum chunk size in bytes */\n static minSize = 256 * KiB;\n /** Initial chunk size in bytes */\n static size = 4 * (256 * KiB);\n static minChunkTime = 8;\n static maxChunkTime = 24;\n\n /**\n * Scales the chunk size based on the throughput.\n * If the elapsed time to upload a chunk is less than the min time, increase the chunk size.\n * If the elapsed time is more than the max time, decrease the chunk size.\n * Keeps the chunk size within the min and max limits.\n * @param throughput - represents the upload rate in bytes/sec.\n */\n static scale(throughput: number): number {\n const elapsedTime = DynamicChunk.size / throughput;\n if (elapsedTime < DynamicChunk.minChunkTime) {\n DynamicChunk.size = Math.min(DynamicChunk.maxSize, DynamicChunk.size * 2);\n }\n if (elapsedTime > DynamicChunk.maxChunkTime) {\n DynamicChunk.size = Math.max(DynamicChunk.minSize, DynamicChunk.size / 2);\n }\n return DynamicChunk.size;\n }\n}\n","function safeMatch(base: string, re: RegExp): string {\n return (base.match(re) || [''])[0];\n}\n\nexport function resolveUrl(url: string, base: string): string {\n if (url.indexOf('https://') * url.indexOf('http://') === 0) return url;\n if (url.indexOf('//') === 0) return safeMatch(base, /^(https?:)/) + url;\n if (url.indexOf('/') === 0) return safeMatch(base, /^(?:https?:)?(?:\\/\\/)?([^\\/?]+)/) + url;\n return safeMatch(base, /^(?:https?:)?(?:\\/\\/)?([^\\/?]+)?(.*\\/)/) + url;\n}\n\n/**\n * Unwraps a value if it is a function, otherwise returns the value directly.\n * Useful for allowing values to optionally be specified as functions.\n */\nexport function unfunc<T, V>(value: T | ((ref: V) => T), ref: V): T {\n return value instanceof Function ? value(ref) : value;\n}\n\nexport const pick = <T, K extends keyof T>(obj: T, props: K[]): Pick<T, K> => {\n const result = {} as Pick<T, K>;\n props.forEach(key => (result[key] = obj[key]));\n return result;\n};\n\nexport function isNumber(x?: unknown): x is number {\n return x === Number(x);\n}\n\n/**\n * 32-bit FNV-1a hash function\n */\nexport function createHash(str: string): number {\n let hash = 2166136261;\n const len = str.length;\n for (let i = 0; i < len; i++) {\n hash ^= str.charCodeAt(i);\n hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);\n }\n return hash >>> 0;\n}\n\n/**\n * Utility functions for base64 encoding and decoding strings and objects.\n */\nexport const b64 = {\n encode: (str: string): string =>\n btoa(\n encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) =>\n String.fromCharCode(Number.parseInt(p1, 16))\n )\n ),\n decode: (str: string): string =>\n decodeURIComponent(\n atob(str)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n ),\n\n serialize: (obj: Record<string, unknown>): string =>\n Object.keys(obj)\n .map(key => [key, b64.encode(String(obj[key]))].filter(Boolean).join(' '))\n .toString(),\n\n parse: (encoded: string): Record<string, string> => {\n const kvPairs = encoded.split(',').map(kv => kv.split(' '));\n const decoded: Record<string, string> = {};\n for (const [key, value] of kvPairs) {\n if (key) decoded[key] = value ? b64.decode(value) : '';\n }\n return decoded;\n }\n};\n\nexport function isBrowser(): boolean {\n return ![typeof window, typeof navigator].includes('undefined');\n}\n\nexport function onLine(): boolean {\n return isBrowser() ? navigator.onLine : true;\n}\n\nexport function isIOS(): boolean {\n return isBrowser() && /iPad|iPhone/.test(navigator.userAgent);\n}\n\nexport function osVersion() {\n const m = /OS (\\d+)_(\\d+)_?(\\d+)?/.exec(navigator.userAgent);\n if (m?.length) return m[1] + '.' + m[2] + '.' + m[3] || '0';\n return;\n}\n\nexport function compareVersions(a: string, b: string): number {\n return a.localeCompare(b, undefined, { numeric: true });\n}\n\nexport function isBadIOS() {\n return isIOS() && compareVersions(osVersion() || '0.0', '15.3') < 0;\n}\n\n/**\n * [Big files upload error with iOS](https://github.com/kukhariev/ngx-uploadx/issues/316)\n * TODO: move to app\n */\nexport const iosOverride = isBadIOS()\n ? { chunkSize: 0, retryConfig: { shouldRetry: () => false } }\n : {};\n","import { Injectable } from '@angular/core';\nimport { Uploader } from './uploader';\nimport { createHash } from './utils';\n\nexport interface UidService {\n generateId(uploader: Uploader): Promise<string> | string;\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class IdService implements UidService {\n generateId(uploader: Uploader): Promise<string> | string {\n const print = JSON.stringify({\n ...uploader.metadata,\n type: uploader.constructor.name,\n endpoint: uploader.endpoint\n });\n return createHash(uploader.name + uploader.size).toString(16) + createHash(print).toString(16);\n }\n}\n","export enum ErrorType {\n NotFound,\n Auth,\n Retryable,\n Fatal\n}\n\nexport type ShouldRetryFunction = (code: number, attempts: number) => boolean;\nexport type KeepPartialFunction = (code: number) => boolean;\n\nexport interface RetryConfig {\n /** Maximum number of retry attempts */\n maxAttempts?: number;\n /** Upload not exist status codes */\n shouldRestartCodes?: number[];\n /** Bad token? status codes */\n authErrorCodes?: number[];\n /** Retryable 4xx status codes */\n shouldRetryCodes?: number[];\n /** Overrides the built-in function that determines whether the operation should be repeated */\n shouldRetry?: ShouldRetryFunction;\n /** The minimum retry delay */\n minDelay?: number;\n /** The maximum retry delay */\n maxDelay?: number;\n /** Delay used between retries for non-error responses with missing range/offset */\n onBusyDelay?: number;\n /** Time interval after which hanged requests must be retried */\n timeout?: number;\n /** Determines whether partial chunks should be kept */\n keepPartial?: boolean | KeepPartialFunction;\n}\n\nconst defaultRetryConfig: Required<RetryConfig> = {\n maxAttempts: 8,\n shouldRestartCodes: [404, 410],\n authErrorCodes: [401],\n shouldRetryCodes: [408, 423, 429, 460],\n shouldRetry(code: number): boolean {\n return code < 400 || code >= 500 || this.shouldRetryCodes.includes(code);\n },\n minDelay: 500,\n maxDelay: 50000,\n onBusyDelay: 1000,\n timeout: 0,\n keepPartial(statusCode): boolean {\n return statusCode >= 400;\n }\n};\n\n/**\n * Retryable ErrorHandler\n */\nexport class RetryHandler {\n public attempts = 0;\n config: Required<RetryConfig>;\n private observedValue?: string | number;\n cancel: () => void = () => {};\n\n constructor(configOptions: RetryConfig = {}) {\n this.config = Object.assign({}, defaultRetryConfig, configOptions);\n }\n\n /**\n * Determine error type based on response code\n * @param code - HTTP response status code\n */\n kind(code: number): ErrorType {\n this.attempts++;\n if (this.attempts > this.config.maxAttempts) {\n return ErrorType.Fatal;\n }\n if (this.config.authErrorCodes.includes(code)) {\n return ErrorType.Auth;\n }\n if (this.config.shouldRestartCodes.includes(code)) {\n return ErrorType.NotFound;\n }\n if (this.config.shouldRetry(code, this.attempts)) {\n return ErrorType.Retryable;\n }\n return ErrorType.Fatal;\n }\n\n /**\n * Wait before next retry attempt\n * @param time - Delay in ms\n */\n wait(time?: number): Promise<void> {\n const ms =\n time || Math.min(2 ** (this.attempts - 1) * this.config.minDelay, this.config.maxDelay);\n const jitter = Math.floor(Math.random() * this.config.minDelay * this.attempts);\n let id: ReturnType<typeof setTimeout>;\n return new Promise(resolve => {\n this.cancel = () => {\n clearTimeout(id);\n resolve();\n };\n id = setTimeout(this.cancel, ms + jitter);\n });\n }\n\n /**\n * Observes value to reset retry attempts counter\n * @param value - Value to observe\n */\n observe(value?: string | number): void {\n this.observedValue !== value && (this.attempts = 0);\n this.observedValue = value;\n }\n}\n","const HOUR = 1000 * 60 * 60;\n\nexport class Store<T = string> {\n private ttl = 24 * HOUR;\n constructor(readonly prefix = 'UPLOADX-v4.0-') {}\n\n set(key: string, value: T): void {\n this.ttl &&\n localStorage.setItem(this.prefix + key, JSON.stringify([value, Date.now() + this.ttl]));\n }\n\n get(key: string): T | null {\n const item = localStorage.getItem(this.prefix + key);\n if (item) {\n try {\n const [value, expires] = JSON.parse(item) as [T, number];\n if (Date.now() < expires) {\n return value;\n }\n } catch {}\n this.delete(key);\n }\n return null;\n }\n\n delete(key: string): void {\n localStorage.removeItem(this.prefix + key);\n }\n\n clear(maxAgeHours = 0): void {\n this.ttl = maxAgeHours * HOUR;\n const now = Date.now();\n this.keys().forEach(key => {\n const item = localStorage.getItem(key);\n if (item && maxAgeHours) {\n try {\n const [, expires] = JSON.parse(item) as [T, number];\n now > expires && localStorage.removeItem(key);\n return;\n } catch {}\n }\n localStorage.removeItem(key);\n });\n }\n\n private keys(): string[] {\n return Object.keys(localStorage).filter(key => key.indexOf(this.prefix) === 0);\n }\n}\n\nexport const store = isLocalStorageAvailable() ? new Store() : new Map<string, string>();\n\nexport function isLocalStorageAvailable(): boolean {\n try {\n const key = 'LocalStorageTest';\n const value = 'value';\n localStorage.setItem(key, value);\n const getValue = localStorage.getItem(key);\n localStorage.removeItem(key);\n return getValue === value;\n } catch {\n return false;\n }\n}\n","import { Ajax, AjaxRequestConfig } from './ajax';\nimport { Canceler } from './canceler';\nimport { DynamicChunk } from './dynamic-chunk';\nimport {\n AuthorizeRequest,\n Metadata,\n PreRequest,\n RequestConfig,\n RequestHeaders,\n RequestOptions,\n ResponseBody,\n UploadAction,\n UploaderOptions,\n UploadState,\n UploadStatus,\n UploadxControlEvent\n} from './interfaces';\nimport { ErrorType, RetryHandler } from './retry-handler';\nimport { store } from './store';\nimport { isNumber, unfunc } from './utils';\n\nconst actionToStatusMap: { [K in UploadAction]: UploadStatus } = {\n pause: 'paused',\n upload: 'queue',\n cancel: 'cancelled',\n update: 'updated'\n};\n\n/**\n * Uploader Base Class\n */\nexport abstract class Uploader implements UploadState {\n name: string;\n readonly size: number;\n readonly uploadId!: string;\n response: ResponseBody = null;\n responseStatus = 0;\n responseHeaders: Record<string, string> = {};\n progress = 0;\n remaining!: number;\n speed = 0;\n /** Custom headers */\n headers: RequestHeaders = {};\n /** Metadata Object */\n metadata: Metadata;\n /** Upload endpoint */\n endpoint = '/upload';\n /** Chunk size in bytes */\n chunkSize = 0;\n /** Auth token/tokenGetter */\n token: UploadxControlEvent['token'];\n /** Byte offset within the whole file */\n offset?: number;\n /** Retries handler */\n retry: RetryHandler;\n canceler = new Canceler();\n abortController = new AbortController();\n /** Set HttpRequest responseType */\n responseType?: 'json' | 'text' | 'document';\n protected _authorize: AuthorizeRequest;\n protected _prerequest: PreRequest;\n protected _token!: string;\n private _eventsCount = 0;\n\n constructor(\n readonly file: File,\n readonly options: Readonly<UploaderOptions>,\n readonly stateChange: (uploader: Uploader) => void,\n readonly ajax: Ajax\n ) {\n this.retry = new RetryHandler(options.retryConfig);\n this.name = file.name;\n this.size = file.size;\n this.metadata = {\n name: file.name,\n mimeType: file.type || 'application/octet-stream',\n size: file.size,\n lastModified: file.lastModified\n };\n options.maxChunkSize && (DynamicChunk.maxSize = options.maxChunkSize);\n /** A function that is executed before the request is sent */\n this._prerequest = options.prerequest || (req => req);\n /** A function used to authorize the request */\n this._authorize = options.authorize || (req => req);\n this.configure(options);\n }\n\n private _url = '';\n\n get url(): string {\n return this._url || store.get(this.uploadId) || '';\n }\n\n set url(value: string) {\n this._url !== value && store.set(this.uploadId, value);\n this._url = value;\n }\n\n private _status!: UploadStatus;\n\n get status(): UploadStatus {\n return this._status;\n }\n\n set status(s: UploadStatus) {\n if (s !== 'updated' && s !== 'cancelled') {\n if (this._status === s) return;\n if (this._status === 'complete') return;\n }\n if (this._status === 'cancelled') return;\n if (this._status === 'uploading' && s === 'queue') return;\n if (this._status === 'retry') this.retry.cancel();\n this._status = s;\n if (s === 'paused') this.abort();\n if (s === 'cancelled' || s === 'complete' || s === 'error') this.cleanup();\n if (s === 'cancelled') this.cancelAndSendState();\n else if (s === 'updated') this.updateAndSendState();\n else this.stateChange(this);\n }\n\n /**\n * Configure uploader\n */\n configure({ metadata, headers, token, endpoint, action }: UploadxControlEvent): void {\n endpoint && (this.endpoint = endpoint);\n token && (this.token = token);\n metadata && Object.assign(this.metadata, unfunc(metadata, this.file));\n headers && Object.assign(this.headers, unfunc(headers, this.file));\n action && (this.status = actionToStatusMap[action]);\n }\n\n /**\n * Starts uploading\n */\n async upload(): Promise<void> {\n do {\n this.status = 'uploading';\n try {\n this._token ||= await this.updateToken();\n this.url ||= await this.getFileUrl();\n if (this.offset !== this.size) {\n this.offset = isNumber(this.offset)\n ? await this.sendFileContent()\n : await this.getOffset();\n }\n this.retry.observe(this.offset);\n if (this.offset === this.size) {\n this.remaining = 0;\n this.progress = 100;\n this.status = 'complete';\n } else if (!isNumber(this.offset)) {\n this.stateChange(this);\n await this.retry.wait(this.getRetryAfterFromBackend() || this.retry.config.onBusyDelay);\n }\n } catch (e) {\n e instanceof Error && console.error(e);\n if (this.status !== 'uploading') {\n return;\n }\n switch (this.retry.kind(this.responseStatus)) {\n case ErrorType.Fatal:\n this.status = 'error';\n return;\n case ErrorType.NotFound:\n this.url = '';\n break;\n case ErrorType.Auth:\n this._token = '';\n break;\n default:\n if (unfunc(this.retry.config.keepPartial, this.responseStatus)) {\n this.offset = undefined;\n }\n this.status = 'retry';\n await this.retry.wait(this.getRetryAfterFromBackend());\n }\n }\n } while (['uploading', 'retry', 'updated'].includes(this._status));\n }\n\n /**\n * Performs http requests\n */\n async request(requestOptions: RequestOptions): Promise<void> {\n this.responseStatus = 0;\n this.response = null;\n this.responseHeaders = {};\n if (this.abortController.signal.aborted) {\n this.abortController = new AbortController();\n }\n const signal = requestOptions.signal || this.abortController.signal;\n let req: RequestConfig = {\n body: requestOptions.body || null,\n canceler: this.canceler,\n signal,\n headers: { ...this.headers, ...requestOptions.headers },\n method: requestOptions.method || 'GET',\n url: requestOptions.url || this.url\n };\n if (!requestOptions.skipAuthorization) {\n req = await this._authorize(req, this._token);\n }\n const { body = null, headers, method, url = req.url } = (await this._prerequest(req)) || req;\n const ajaxRequestConfig: AjaxRequestConfig = {\n method,\n headers: { ...req.headers, ...headers },\n url,\n data: body,\n responseType: this.options.responseType ?? this.responseType,\n withCredentials: !!this.options.withCredentials,\n canceler: this.canceler,\n signal,\n validateStatus: () => true,\n timeout: this.retry.config.timeout\n };\n if (isNumber(this.offset) && body && typeof body === 'object') {\n ajaxRequestConfig.onUploadProgress = this.onProgress();\n }\n const response = await this.ajax.request(ajaxRequestConfig);\n this.response = response.data;\n this.responseHeaders = response.headers;\n this.responseStatus = response.status;\n if (response.status >= 400) {\n return Promise.reject();\n }\n }\n\n /**\n * Set auth token string\n */\n updateToken = (): string | Promise<string> => {\n return unfunc(this.token || '', this.responseStatus);\n };\n\n /**\n * Get file URI\n */\n protected abstract getFileUrl(): Promise<string>;\n\n /**\n * Send file content and return an offset for the next request\n */\n protected abstract sendFileContent(): Promise<number | undefined>;\n\n /**\n * Get an offset for the next request\n */\n protected abstract getOffset(): Promise<number | undefined>;\n\n /**\n * Updating the metadata of the upload\n */\n protected update<T = { metadata?: Metadata }>(_data: T): Promise<string> {\n return Promise.reject('Not implemented');\n }\n\n protected abort(): void {\n this.offset = undefined;\n this.abortController.abort();\n this.canceler.cancel();\n }\n\n protected async cancel(): Promise<void> {\n this.abort();\n if (this.url) {\n await this.request({ method: 'DELETE' }).catch(() => {});\n }\n }\n\n /**\n * Gets the value from the response\n */\n protected getValueFromResponse(key: string): string | null {\n return this.responseHeaders[key.toLowerCase()] || null;\n }\n\n /**\n * Get file chunk\n * @param offset - number of bytes of the file to skip\n * @param size - chunk size\n */\n getChunk(offset?: number, size?: number): { start: number; end: number; body: Blob } {\n if (this.responseStatus === 413) {\n DynamicChunk.maxSize = DynamicChunk.size = Math.floor(DynamicChunk.size / 2);\n }\n this.chunkSize =\n this.options.chunkSize === 0 ? this.size : this.options.chunkSize || DynamicChunk.size;\n const start = offset ?? this.offset ?? 0;\n const end = Math.min(start + (size || this.chunkSize), this.size);\n const body = this.file.slice(start, end);\n return { start, end, body };\n }\n\n private getRetryAfterFromBackend(): number {\n return Number(this.getValueFromResponse('retry-after')) * 1000;\n }\n\n private cancelAndSendState() {\n this.cancel().then(() => this.stateChange(this), console.error);\n }\n\n private updateAndSendState(): void {\n this.update({ metadata: this.metadata }).then(() => this.stateChange(this), console.error);\n }\n\n private cleanup = () => {\n store.delete(this._url);\n store.delete(this.uploadId);\n };\n\n private onProgress(): (evt: ProgressEvent) => void {\n let throttle: ReturnType<typeof setTimeout> | undefined;\n const startTime = Date.now();\n return ({ loaded }) => {\n const current = loaded / ((Date.now() - startTime) / 1000);\n this.speed = ~~((this.speed * this._eventsCount + current) / ++this._eventsCount);\n DynamicChunk.scale(this.speed);\n if (!throttle) {\n throttle = setTimeout(() => (throttle = undefined), 500);\n const uploaded = (this.offset || 0) + loaded;\n this.progress = +((uploaded / this.size) * 100).toFixed(2);\n this.remaining = ~~((this.size - uploaded) / this.speed);\n this.stateChange(this);\n }\n };\n }\n}\n","import { Uploader } from './uploader';\nimport { resolveUrl } from './utils';\n\n/**\n * Implements XHR/CORS Resumable Upload\n * {@link https://github.com/kukhariev/node-uploadx/blob/master/proto.md Github}\n * @see {@link https://developers.google.com/drive/api/v3/manage-uploads#resumable Google Drive API documentation}\n */\nexport class UploaderX extends Uploader {\n responseType = 'json' as const;\n\n async getFileUrl(): Promise<string> {\n const body = JSON.stringify(this.metadata);\n const headers = {\n 'Content-Type': 'application/json; charset=utf-8',\n 'X-Upload-Content-Length': this.size,\n 'X-Upload-Content-Type': this.file.type || 'application/octet-stream'\n };\n await this.request({ method: 'POST', body, url: this.endpoint, headers });\n this.offset = this.getOffsetFromResponse() || (this.responseStatus === 201 ? 0 : undefined);\n const location = this.getValueFromResponse('location');\n if (!location) {\n throw new Error('Invalid or missing Location header');\n }\n return resolveUrl(location, this.endpoint);\n }\n\n async sendFileContent(): Promise<number | undefined> {\n const { body, start, end } = this.getChunk();\n const headers = {\n 'Content-Type': 'application/octet-stream',\n 'Content-Range': `bytes ${start}-${end - 1}/${this.size}`\n };\n await this.request({ method: 'PUT', body, headers });\n return this.responseStatus > 201 ? this.getOffsetFromResponse() : end;\n }\n\n async getOffset(): Promise<number | undefined> {\n const headers = {\n 'Content-Type': 'application/octet-stream',\n 'Content-Range': `bytes */${this.size}`\n };\n await this.request({ method: 'PUT', headers });\n return this.responseStatus > 201 ? this.getOffsetFromResponse() : this.size;\n }\n\n async update<T>(data: T): Promise<string> {\n const body = JSON.stringify(data);\n const headers = { 'Content-Type': 'application/json; charset=utf-8' };\n await this.request({ method: 'PATCH', body, headers });\n const location = this.getValueFromResponse('location') || this.url;\n return resolveUrl(location, this.endpoint);\n }\n\n protected getOffsetFromResponse(): number | undefined {\n const range = this.getValueFromResponse('Range');\n return range ? getRangeEnd(range) + 1 : undefined;\n }\n}\n\nexport function getRangeEnd(range = ''): number {\n const end = parseInt(range.split(/-/)[1], 10);\n return end >= 0 ? end : -1;\n}\n","import { InjectionToken } from '@angular/core';\nimport { Ajax } from './ajax';\nimport { AuthorizeRequest, UploaderOptions } from './interfaces';\nimport { Uploader } from './uploader';\nimport { UploaderX } from './uploaderx';\n\n/**\n * Global Module Options\n */\nexport interface UploadxOptions extends UploaderOptions {\n /**\n * Provide a user-defined class to support another upload protocol or to extend an existing one.\n * @defaultValue UploadX\n */\n uploaderClass?: UploaderClass;\n /**\n * Set the maximum parallel uploads\n * @defaultValue 2\n */\n concurrency?: number;\n /**\n * Automatically start upload when files added\n * @defaultValue true\n */\n autoUpload?: boolean;\n /**\n * File types the user can pick from the file input\n */\n allowedTypes?: string;\n /**\n * Add 'multiple' attribute\n * @defaultValue true\n */\n multiple?: boolean;\n /**\n * Retention time for incomplete uploads\n * @defaultValue 24\n */\n storeIncompleteHours?: number;\n}\n\nexport interface UploadxFactoryOptions extends UploadxOptions {\n endpoint: string;\n autoUpload: boolean;\n concurrency: number;\n uploaderClass: UploaderClass;\n authorize: AuthorizeRequest;\n storeIncompleteHours: number;\n}\n\nexport type UploaderClass = new (\n file: File,\n options: UploaderOptions,\n stateChange: (uploader: Uploader) => void,\n ajax: Ajax\n) => Uploader;\n\nconst defaultOptions: UploadxFactoryOptions = {\n endpoint: '/upload',\n autoUpload: true,\n concurrency: 2,\n uploaderClass: UploaderX,\n authorize: (req, token) => {\n token && (req.headers['Authorization'] = `Bearer ${token}`);\n return req;\n },\n storeIncompleteHours: 24\n};\n\nexport const UPLOADX_FACTORY_OPTIONS = new InjectionToken<UploadxFactoryOptions>(\n 'uploadx.factory.options',\n { providedIn: 'root', factory: () => defaultOptions }\n);\nexport const UPLOADX_OPTIONS = new InjectionToken<UploadxOptions>('uploadx.options');\n","import { Uploader } from './uploader';\nimport { b64, resolveUrl } from './utils';\n\n/**\n * Implements tus resumable upload protocol\n * {@link https://github.com/tus/tus-resumable-upload-protocol/blob/master/protocol.md Github}\n */\nexport class Tus extends Uploader {\n headers = { 'Tus-Resumable': '1.0.0' };\n\n async getFileUrl(): Promise<string> {\n const encodedMetaData = b64.serialize(this.metadata);\n const headers = {\n 'Upload-Length': this.size,\n 'Upload-Metadata': encodedMetaData\n };\n await this.request({ method: 'POST', url: this.endpoint, headers });\n this.offset = this.getOffsetFromResponse() || (this.responseStatus === 201 ? 0 : undefined);\n const location = this.getValueFromResponse('location');\n if (!location) {\n throw new Error('Invalid or missing Location header');\n }\n return resolveUrl(location, this.endpoint);\n }\n\n async sendFileContent(): Promise<number | undefined> {\n const { body, start, end } = this.getChunk();\n const headers = {\n 'Content-Type': 'application/offset+octet-stream',\n 'Upload-Offset': start\n };\n await this.request({ method: 'PATCH', body, headers });\n return this.getOffsetFromResponse() || end;\n }\n\n async getOffset(): Promise<number | undefined> {\n await this.request({ method: 'HEAD' });\n return this.getOffsetFromResponse();\n }\n\n protected getOffsetFromResponse(): number | undefined {\n const offset = this.getValueFromResponse('Upload-Offset');\n return offset ? parseInt(offset, 10) : undefined;\n }\n}\n","import { inject, Injectable, NgZone, OnDestroy } from '@angular/core';\nimport { fromEvent, Observable, Subject, Subscription } from 'rxjs';\nimport { debounceTime, map } from 'rxjs/operators';\nimport { AjaxRequestConfig, AjaxResponse, UPLOADX_AJAX } from './ajax';\nimport { IdService } from './id.service';\nimport { UploadState, UploadxControlEvent } from './interfaces';\nimport {\n UPLOADX_FACTORY_OPTIONS,\n UPLOADX_OPTIONS,\n UploadxFactoryOptions,\n UploadxOptions\n} from './options';\nimport { store } from './store';\nimport { Uploader } from './uploader';\nimport { iosOverride, isBrowser, onLine, pick } from './utils';\n\nexport const UPLOAD_STATE_KEYS: (keyof UploadState)[] = [\n 'file',\n 'name',\n 'progress',\n 'remaining',\n 'response',\n 'responseHeaders',\n 'responseStatus',\n 'size',\n 'speed',\n 'status',\n 'uploadId',\n 'url'\n];\n\nconst DUE_TIME = 5;\n\n@Injectable({ providedIn: 'root' })\nexport class UploadxService implements OnDestroy {\n /** Upload Queue */\n queue: Uploader[] = [];\n readonly options: UploadxFactoryOptions;\n private readonly eventsStream: Subject<UploadState> = new Subject();\n private subs: Subscription[] = [];\n private ngZone = inject(NgZone);\n readonly ajax = inject(UPLOADX_AJAX);\n private idService = inject(IdService);\n\n constructor() {\n const options = inject(UPLOADX_OPTIONS, { optional: true });\n const defaults = inject(UPLOADX_FACTORY_OPTIONS);\n this.options = Object.assign({}, defaults, options);\n if (isBrowser()) {\n this.subs.push(\n fromEvent(window, 'online').subscribe(() => this.control({ action: 'upload' })),\n fromEvent(window, 'offline').subscribe(() => this.control({ action: 'pause' }))\n );\n }\n }\n\n /** Upload status events */\n get events(): Observable<UploadState> {\n return this.eventsStream.asObservable();\n }\n\n /**\n * Initializes service\n * @param options global module options\n * @returns Observable that emits a new value on progress or status changes\n */\n init(options: UploadxOptions = {}): Observable<UploadState> {\n Object.assign(this.options, options);\n return this.events;\n }\n\n /**\n * Initializes service\n * @param options global module options\n * @returns Observable that emits the current array of uploaders\n */\n connect(options?: UploadxOptions): Observable<Uploader[]> {\n return this.init(options).pipe(\n map(() => this.queue),\n debounceTime(DUE_TIME)\n );\n }\n\n /**\n * Terminates all uploads and clears the queue\n */\n disconnect(): void {\n this.queue.forEach(uploader => (uploader.status = 'paused'));\n this.queue = [];\n }\n\n /**\n * Returns current uploads state\n * @example\n * // restore background uploads\n * this.uploads = this.uploadService.state();\n */\n state(): UploadState[] {\n return this.queue.map(uploader => pick(uploader, UPLOAD_STATE_KEYS));\n }\n\n ngOnDestroy(): void {\n this.disconnect();\n this.subs.forEach(sub => sub.unsubscribe());\n }\n\n /**\n * Creates uploaders for files and adds them to the upload queue\n */\n handleFiles(files: FileList | File | File[], options = {} as UploadxOptions): void {\n const instanceOptions: UploadxFactoryOptions = { ...this.options, ...iosOverride, ...options };\n store.clear(instanceOptions.storeIncompleteHours);\n this.options.concurrency = instanceOptions.concurrency;\n ('name' in files ? [files] : Array.from(files)).forEach(file =>\n this.addUploaderInstance(file, instanceOptions)\n );\n }\n\n /**\n * Upload control\n * @example\n * // pause all\n * this.uploadService.control({ action: 'pause' });\n * // pause upload with uploadId\n * this.uploadService.control({ action: 'pause', uploadId});\n * // set token\n * this.uploadService.control({ token: `TOKEN` });\n */\n control(evt: UploadxControlEvent): void {\n const target = evt.uploadId\n ? this.queue.filter(({ uploadId }) => uploadId === evt.uploadId)\n : this.queue;\n target.forEach(uploader => uploader.configure(evt));\n }\n\n /**\n * Number of active uploads\n */\n get activeUploadsCount(): number {\n return this.queue.filter(({ status }) => status === 'uploading' || status === 'retry').length;\n }\n\n /**\n * Performs http requests\n */\n async request<T = string>(config: AjaxRequestConfig): Promise<AjaxResponse<T>> {\n config.data ||= config.body;\n return this.ajax.request(config);\n }\n\n private stateChange = (uploader: Uploader) => {\n this.ngZone.run(() => this.eventsStream.next(pick(uploader, UPLOAD_STATE_KEYS)));\n if (uploader.status !== 'uploading' && uploader.status !== 'added') {\n this.ngZone.runOutsideAngular(() => setTimeout(() => this.processQueue()));\n }\n };\n\n private async addUploaderInstance(file: File, options: UploadxFactoryOptions): Promise<void> {\n const uploader = new options.uploaderClass(file, options, this.stateChange, this.ajax);\n (uploader as { uploadId: string }).uploadId = await this.idService.generateId(uploader);\n this.queue.push(uploader);\n uploader.status = 'added';\n if (options.autoUpload && onLine()) {\n uploader.status = 'queue';\n }\n }\n\n private processQueue(): void {\n this.queue = this.queue.filter(({ status }) => status !== 'cancelled');\n this.queue\n .filter(({ status }) => status === 'queue')\n .slice(0, Math.max(this.options.concurrency - this.activeUploadsCount, 0))\n .forEach(uploader => uploader.upload());\n }\n}\n","import { ElementRef, EventEmitter, HostListener, Input, Output, Renderer2 } from '@angular/core';\nimport { Directive, OnInit } from '@angular/core';\nimport { takeWhile } from 'rxjs/operators';\nimport { UploadState, UploadxControlEvent } from './interfaces';\nimport { UploadxOptions } from './options';\nimport { UploadxService } from './uploadx.service';\n\n@Directive({\n selector: '[uploadx]',\n standalone: true\n})\nexport class UploadxDirective implements OnInit {\n @Input()\n set uploadx(value: UploadxOptions | '') {\n if (value) {\n this.options = value;\n }\n }\n\n @Input() options: UploadxOptions = {};\n\n @Input() set control(value: UploadxControlEvent | '') {\n if (value) {\n this.uploadService.control(value);\n }\n }\n\n @Output() state = new EventEmitter<UploadState>();\n\n constructor(\n private elementRef: ElementRef,\n private renderer: Renderer2,\n private uploadService: UploadxService\n ) {}\n\n ngOnInit(): void {\n const { multiple, allowedTypes } = { ...this.uploadService.options, ...this.options };\n multiple !== false && this.renderer.setAttribute(this.elementRef.nativeElement, 'multiple', '');\n allowedTypes &&\n this.renderer.setAttribute(this.elementRef.nativeElement, 'accept', allowedTypes);\n\n this.uploadService.events\n .pipe(takeWhile(() => this.state.observers.length > 0))\n .subscribe(this.state);\n }\n\n @HostListener('change', ['$event.target.files'])\n fileListener(files?: FileList | File[]): void {\n if (files?.length) {\n this.uploadService.handleFiles(files, this.options);\n }\n }\n}\n","import { ContentChild, Directive, HostBinding, HostListener } from '@angular/core';\nimport { UploadxDirective } from './uploadx.directive';\nimport { UploadxService } from './uploadx.service';\n\n@Directive({\n selector: '[uploadxDrop]',\n standalone: true\n})\nexport class UploadxDropDirective {\n @HostBinding('class.uploadx-drop-active')\n active = false;\n\n @ContentChild(UploadxDirective, { static: false })\n fileInput?: UploadxDirective;\n\n constructor(private uploadService: UploadxService) {}\n\n @HostListener('drop', ['$event'])\n dropHandler(event: DragEvent): void {\n this._stopEvents(event);\n this.active = false;\n const files = this.getFiles(event);\n if (files.length) {\n this.fileInput ? this.fileInput.fileListener(files) : this.uploadService.handleFiles(files);\n }\n }\n\n @HostListener('dragover', ['$event'])\n onDragOver(event: DragEvent): void {\n this._stopEvents(event);\n if (event.dataTransfer?.items[0]?.kind === 'file') {\n if (this.fileInput?.options.multiple === false && event.dataTransfer.items.length > 1) {\n event.dataTransfer.dropEffect = 'none';\n } else {\n event.dataTransfer.dropEffect = 'copy';\n this.active = true;\n }\n }\n }\n\n @HostListener('dragleave', ['$event'])\n onDragLeave(event: DragEvent): void {\n this._stopEvents(event);\n this.active = false;\n }\n\n /**\n * Extracts the files from a `DragEvent` object\n */\n getFiles(event: DragEvent): FileList | File[] {\n const dataTransfer = new DataTransfer();\n const items = event.dataTransfer?.items;\n if (items?.length) {\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n if (item.kind === 'file' && !item.webkitGetAsEntry()?.isDirectory) {\n const file = item.getAsFile();\n file && dataTransfer.items.add(file);\n }\n }\n }\n return dataTransfer.files;\n }\n\n protected _stopEvents(event: DragEvent): void {\n event.stopPropagation();\n event.preventDefault();\n }\n}\n","import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { UploadxOptions, UPLOADX_OPTIONS } from './options';\nimport { UploadxDropDirective } from './uploadx-drop.directive';\nimport { UploadxDirective } from './uploadx.directive';\n\n@NgModule({\n imports: [UploadxDirective, UploadxDropDirective],\n exports: [UploadxDirective, UploadxDropDirective]\n})\nexport class UploadxModule {\n static withConfig(options: UploadxOptions): ModuleWithProviders<UploadxModule> {\n return {\n ngModule: UploadxModule,\n providers: [{ provide: UPLOADX_OPTIONS, useValue: options }]\n };\n }\n}\n","import { Provider } from '@angular/core';\nimport { UPLOADX_OPTIONS, UploadxOptions } from './options';\n\n/**\n * Provides configuration options for standalone app.\n *\n * @example\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideUploadx({\n * endpoint: uploadUrl,\n * allowedTypes: 'video/*,audio/*',\n * maxChunkSize: 96 * 1024 * 1024\n * })\n * ]\n * });\n * ```\n */\nexport function provideUploadx(options: UploadxOptions = {}) {\n const providers: Provider[] = [\n {\n provide: UPLOADX_OPTIONS,\n useValue: options\n }\n ];\n return providers;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.UploadxService"],"mappings":"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