From 9395781a6fe5743276e23ec57b55aee136ee7f95 Mon Sep 17 00:00:00 2001 From: Arnab Sen Date: Tue, 17 Jun 2025 18:32:04 +0530 Subject: [PATCH 1/3] fix: handle stream end properly in readChunk to avoid hanging on deferred-length uploads --- docs/contributing.md | 3 +- lib/node/sources/NodeStreamFileSource.ts | 18 +++++-- test/spec/test-node-specific.js | 68 ++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index 9f638ab0..6bfc2974 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -29,7 +29,8 @@ Tests are implemented using Jasmine and can be found in the `test/` directory. T - To run the tests inside **Node.js** use the command `yarn run test-node`. - To run the tests inside **Puppeteer** (an automated browser) use the command `yarn run test-puppeteer`. -- To run the tests in your browser open `test/SpecRunner.html` in a browser and you should see a visual representation of the test results. No web server is required, you can open `SpecRunner.html` using the `file:///` protocol. +- To run the tests in your browser open `test/SpecRunner.html` in a browser, and you should see a visual representation of the + test results. No web server is required, you can open `SpecRunner.html` using the `file:///` protocol. - To run the tests on BrowserStack's cloud testing infrastructure use `yarn run test-browserstack`. Before using this command, you have to set up your BrowserStack account by filling the `BROWSERSTACK_USERNAME` and `BROWSERSTACK_KEY` variables or else the command will fail. Also note that you have to rebuild the library before you run the tests. So either you automatically let `yarn run watch` listen for file changes and automatically rebuild the needed artifacts, or you run `yarn run build` on your own before you execute the tests. diff --git a/lib/node/sources/NodeStreamFileSource.ts b/lib/node/sources/NodeStreamFileSource.ts index 143f2f25..f5f2a687 100644 --- a/lib/node/sources/NodeStreamFileSource.ts +++ b/lib/node/sources/NodeStreamFileSource.ts @@ -13,7 +13,7 @@ import type { FileSource } from '../../options.js' async function readChunk(stream: Readable, size: number) { return new Promise((resolve, reject) => { const onError = (err: Error) => { - stream.off('readable', onReadable) + cleanup() reject(err) } @@ -22,15 +22,25 @@ async function readChunk(stream: Readable, size: number) { const chunk = stream.read(size) if (chunk != null) { - stream.off('error', onError) - stream.off('readable', onReadable) - + cleanup() resolve(chunk) } } + const onEnd = () => { + cleanup() + resolve(Buffer.alloc(0)) + } + + const cleanup = () => { + stream.off('error', onError) + stream.off('readable', onReadable) + stream.off('end', onEnd) + } + stream.once('error', onError) stream.on('readable', onReadable) + stream.once('end', onEnd) }) } diff --git a/test/spec/test-node-specific.js b/test/spec/test-node-specific.js index 535ae146..e3466df8 100644 --- a/test/spec/test-node-specific.js +++ b/test/spec/test-node-specific.js @@ -106,6 +106,19 @@ describe('tus', () => { await expectHelloWorldUpload(input, options) }) + it('should upload with deferred length and chunkSize divides size exactly', async () => { + const input = new stream.PassThrough() + const options = { + httpStack: new TestHttpStack(), + endpoint: '/uploads', + chunkSize: 7, + uploadLengthDeferred: true, + } + + input.end('my hello WORLD') + await expectHelloWorldUploadWithDeferred(input, options) + }) + it('should throw an error if the source provides less data than uploadSize', async () => { const input = new stream.PassThrough() input.end('hello world') @@ -561,3 +574,58 @@ async function expectHelloWorldUpload(input, options) { await options.onSuccess.toBeCalled() } + + +//TODO: duplicates some logic from similar tests — can be refactored in a follow-up for maintainability +async function expectHelloWorldUploadWithDeferred(input, options) { + options.httpStack = new TestHttpStack() + options.onSuccess = waitableFunction('onSuccess') + + const upload = new Upload(input, options) + upload.start() + + let req = await options.httpStack.nextRequest() + expect(req.url).toBe('/uploads') + expect(req.method).toBe('POST') + expect(req.requestHeaders['Upload-Length']).toBe(undefined) + expect(req.requestHeaders['Upload-Defer-Length']).toBe('1') + req.respondWith({ + status: 201, + responseHeaders: { Location: '/uploads/blargh' }, + }) + + req = await options.httpStack.nextRequest() + expect(req.url).toBe('/uploads/blargh') + expect(req.method).toBe('PATCH') + expect(req.requestHeaders['Upload-Offset']).toBe('0') + expect(req.bodySize).toBe(7) + req.respondWith({ + status: 204, + responseHeaders: { 'Upload-Offset': '7' }, + }) + + req = await options.httpStack.nextRequest() + expect(req.url).toBe('/uploads/blargh') + expect(req.method).toBe('PATCH') + expect(req.requestHeaders['Upload-Offset']).toBe('7') + expect(req.requestHeaders['Upload-Length']).toBe(undefined) + expect(req.bodySize).toBe(7) + req.respondWith({ + status: 204, + responseHeaders: { 'Upload-Offset': '14' }, + }) + + req = await options.httpStack.nextRequest() + expect(req.url).toBe('/uploads/blargh') + expect(req.method).toBe('PATCH') + expect(req.requestHeaders['Upload-Offset']).toBe('14') + expect(req.requestHeaders['Upload-Length']).toBe('14') + expect(req.bodySize).toBe(0) + req.respondWith({ + status: 204, + responseHeaders: { 'Upload-Offset': '14' }, + }) + + await options.onSuccess.toBeCalled() +} + From e39cd8b4dff0690e3b600c6e5976612f82cb7d72 Mon Sep 17 00:00:00 2001 From: Arnab Sen Date: Tue, 17 Jun 2025 19:36:47 +0530 Subject: [PATCH 2/3] fix: lint issue --- test/spec/test-node-specific.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/spec/test-node-specific.js b/test/spec/test-node-specific.js index e3466df8..85c93cfd 100644 --- a/test/spec/test-node-specific.js +++ b/test/spec/test-node-specific.js @@ -575,7 +575,6 @@ async function expectHelloWorldUpload(input, options) { await options.onSuccess.toBeCalled() } - //TODO: duplicates some logic from similar tests — can be refactored in a follow-up for maintainability async function expectHelloWorldUploadWithDeferred(input, options) { options.httpStack = new TestHttpStack() @@ -628,4 +627,3 @@ async function expectHelloWorldUploadWithDeferred(input, options) { await options.onSuccess.toBeCalled() } - From edf17ccd09d42be102e2de59c70e296b2c2d3417 Mon Sep 17 00:00:00 2001 From: Arnab Sen Date: Mon, 8 Sep 2025 12:32:46 +0530 Subject: [PATCH 3/3] Add built output for temporary production use --- .gitignore | 2 - lib.cjs/DetailedError.d.ts | 7 + lib.cjs/DetailedError.js | 25 + lib.cjs/DetailedError.js.map | 1 + lib.cjs/NoopUrlStorage.d.ts | 7 + lib.cjs/NoopUrlStorage.js | 19 + lib.cjs/NoopUrlStorage.js.map | 1 + lib.cjs/browser/BrowserFileReader.d.ts | 4 + lib.cjs/browser/BrowserFileReader.js | 33 + lib.cjs/browser/BrowserFileReader.js.map | 1 + lib.cjs/browser/FetchHttpStack.d.ts | 30 + lib.cjs/browser/FetchHttpStack.js | 77 ++ lib.cjs/browser/FetchHttpStack.js.map | 1 + lib.cjs/browser/XHRHttpStack.d.ts | 5 + lib.cjs/browser/XHRHttpStack.js | 86 ++ lib.cjs/browser/XHRHttpStack.js.map | 1 + lib.cjs/browser/fileSignature.d.ts | 5 + lib.cjs/browser/fileSignature.js | 37 + lib.cjs/browser/fileSignature.js.map | 1 + lib.cjs/browser/index.d.ts | 47 + lib.cjs/browser/index.js | 39 + lib.cjs/browser/index.js.map | 1 + lib.cjs/browser/urlStorage.d.ts | 9 + lib.cjs/browser/urlStorage.js | 79 ++ lib.cjs/browser/urlStorage.js.map | 1 + lib.cjs/commonFileReader.d.ts | 7 + lib.cjs/commonFileReader.js | 50 + lib.cjs/commonFileReader.js.map | 1 + lib.cjs/cordova/isCordova.d.ts | 2 + lib.cjs/cordova/isCordova.js | 7 + lib.cjs/cordova/isCordova.js.map | 1 + lib.cjs/cordova/readAsByteArray.d.ts | 6 + lib.cjs/cordova/readAsByteArray.js | 28 + lib.cjs/cordova/readAsByteArray.js.map | 1 + lib.cjs/logger.d.ts | 2 + lib.cjs/logger.js | 15 + lib.cjs/logger.js.map | 1 + lib.cjs/node/FileUrlStorage.d.ts | 16 + lib.cjs/node/FileUrlStorage.js | 90 ++ lib.cjs/node/FileUrlStorage.js.map | 1 + lib.cjs/node/NodeFileReader.d.ts | 4 + lib.cjs/node/NodeFileReader.js | 52 + lib.cjs/node/NodeFileReader.js.map | 1 + lib.cjs/node/NodeHttpStack.d.ts | 26 + lib.cjs/node/NodeHttpStack.js | 259 +++++ lib.cjs/node/NodeHttpStack.js.map | 1 + lib.cjs/node/fileSignature.d.ts | 2 + lib.cjs/node/fileSignature.js | 59 ++ lib.cjs/node/fileSignature.js.map | 1 + lib.cjs/node/index.d.ts | 47 + lib.cjs/node/index.js | 38 + lib.cjs/node/index.js.map | 1 + .../node/sources/NodeStreamFileSource.d.ts | 29 + lib.cjs/node/sources/NodeStreamFileSource.js | 120 +++ .../node/sources/NodeStreamFileSource.js.map | 1 + lib.cjs/node/sources/PathFileSource.d.ts | 17 + lib.cjs/node/sources/PathFileSource.js | 61 ++ lib.cjs/node/sources/PathFileSource.js.map | 1 + lib.cjs/options.d.ts | 130 +++ lib.cjs/options.js | 7 + lib.cjs/options.js.map | 1 + lib.cjs/package.json | 1 + lib.cjs/reactnative/isReactNative.d.ts | 3 + lib.cjs/reactnative/isReactNative.js | 13 + lib.cjs/reactnative/isReactNative.js.map | 1 + lib.cjs/reactnative/uriToBlob.d.ts | 6 + lib.cjs/reactnative/uriToBlob.js | 24 + lib.cjs/reactnative/uriToBlob.js.map | 1 + .../sources/ArrayBufferViewFileSource.d.ts | 15 + lib.cjs/sources/ArrayBufferViewFileSource.js | 32 + .../sources/ArrayBufferViewFileSource.js.map | 1 + lib.cjs/sources/BlobFileSource.d.ts | 11 + lib.cjs/sources/BlobFileSource.js | 36 + lib.cjs/sources/BlobFileSource.js.map | 1 + lib.cjs/sources/WebStreamFileSource.d.ts | 16 + lib.cjs/sources/WebStreamFileSource.js | 111 ++ lib.cjs/sources/WebStreamFileSource.js.map | 1 + lib.cjs/upload.d.ts | 189 ++++ lib.cjs/upload.js | 987 ++++++++++++++++++ lib.cjs/upload.js.map | 1 + lib.cjs/uuid.d.ts | 13 + lib.cjs/uuid.js | 23 + lib.cjs/uuid.js.map | 1 + lib.esm/DetailedError.d.ts | 7 + lib.esm/DetailedError.js | 21 + lib.esm/DetailedError.js.map | 1 + lib.esm/NoopUrlStorage.d.ts | 7 + lib.esm/NoopUrlStorage.js | 15 + lib.esm/NoopUrlStorage.js.map | 1 + lib.esm/browser/BrowserFileReader.d.ts | 4 + lib.esm/browser/BrowserFileReader.js | 29 + lib.esm/browser/BrowserFileReader.js.map | 1 + lib.esm/browser/FetchHttpStack.d.ts | 30 + lib.esm/browser/FetchHttpStack.js | 73 ++ lib.esm/browser/FetchHttpStack.js.map | 1 + lib.esm/browser/XHRHttpStack.d.ts | 5 + lib.esm/browser/XHRHttpStack.js | 82 ++ lib.esm/browser/XHRHttpStack.js.map | 1 + lib.esm/browser/fileSignature.d.ts | 5 + lib.esm/browser/fileSignature.js | 34 + lib.esm/browser/fileSignature.js.map | 1 + lib.esm/browser/index.d.ts | 47 + lib.esm/browser/index.js | 33 + lib.esm/browser/index.js.map | 1 + lib.esm/browser/urlStorage.d.ts | 9 + lib.esm/browser/urlStorage.js | 75 ++ lib.esm/browser/urlStorage.js.map | 1 + lib.esm/commonFileReader.d.ts | 7 + lib.esm/commonFileReader.js | 46 + lib.esm/commonFileReader.js.map | 1 + lib.esm/cordova/isCordova.d.ts | 2 + lib.esm/cordova/isCordova.js | 4 + lib.esm/cordova/isCordova.js.map | 1 + lib.esm/cordova/readAsByteArray.d.ts | 6 + lib.esm/cordova/readAsByteArray.js | 25 + lib.esm/cordova/readAsByteArray.js.map | 1 + lib.esm/logger.d.ts | 2 + lib.esm/logger.js | 11 + lib.esm/logger.js.map | 1 + lib.esm/node/FileUrlStorage.d.ts | 16 + lib.esm/node/FileUrlStorage.js | 86 ++ lib.esm/node/FileUrlStorage.js.map | 1 + lib.esm/node/NodeFileReader.d.ts | 4 + lib.esm/node/NodeFileReader.js | 45 + lib.esm/node/NodeFileReader.js.map | 1 + lib.esm/node/NodeHttpStack.d.ts | 26 + lib.esm/node/NodeHttpStack.js | 219 ++++ lib.esm/node/NodeHttpStack.js.map | 1 + lib.esm/node/fileSignature.d.ts | 2 + lib.esm/node/fileSignature.js | 23 + lib.esm/node/fileSignature.js.map | 1 + lib.esm/node/index.d.ts | 47 + lib.esm/node/index.js | 32 + lib.esm/node/index.js.map | 1 + .../node/sources/NodeStreamFileSource.d.ts | 29 + lib.esm/node/sources/NodeStreamFileSource.js | 116 ++ .../node/sources/NodeStreamFileSource.js.map | 1 + lib.esm/node/sources/PathFileSource.d.ts | 17 + lib.esm/node/sources/PathFileSource.js | 56 + lib.esm/node/sources/PathFileSource.js.map | 1 + lib.esm/options.d.ts | 130 +++ lib.esm/options.js | 4 + lib.esm/options.js.map | 1 + lib.esm/package.json | 1 + lib.esm/reactnative/isReactNative.d.ts | 3 + lib.esm/reactnative/isReactNative.js | 9 + lib.esm/reactnative/isReactNative.js.map | 1 + lib.esm/reactnative/uriToBlob.d.ts | 6 + lib.esm/reactnative/uriToBlob.js | 21 + lib.esm/reactnative/uriToBlob.js.map | 1 + .../sources/ArrayBufferViewFileSource.d.ts | 15 + lib.esm/sources/ArrayBufferViewFileSource.js | 28 + .../sources/ArrayBufferViewFileSource.js.map | 1 + lib.esm/sources/BlobFileSource.d.ts | 11 + lib.esm/sources/BlobFileSource.js | 32 + lib.esm/sources/BlobFileSource.js.map | 1 + lib.esm/sources/WebStreamFileSource.d.ts | 16 + lib.esm/sources/WebStreamFileSource.js | 107 ++ lib.esm/sources/WebStreamFileSource.js.map | 1 + lib.esm/upload.d.ts | 189 ++++ lib.esm/upload.js | 979 +++++++++++++++++ lib.esm/upload.js.map | 1 + lib.esm/uuid.d.ts | 13 + lib.esm/uuid.js | 20 + lib.esm/uuid.js.map | 1 + 165 files changed, 5998 insertions(+), 2 deletions(-) create mode 100644 lib.cjs/DetailedError.d.ts create mode 100644 lib.cjs/DetailedError.js create mode 100644 lib.cjs/DetailedError.js.map create mode 100644 lib.cjs/NoopUrlStorage.d.ts create mode 100644 lib.cjs/NoopUrlStorage.js create mode 100644 lib.cjs/NoopUrlStorage.js.map create mode 100644 lib.cjs/browser/BrowserFileReader.d.ts create mode 100644 lib.cjs/browser/BrowserFileReader.js create mode 100644 lib.cjs/browser/BrowserFileReader.js.map create mode 100644 lib.cjs/browser/FetchHttpStack.d.ts create mode 100644 lib.cjs/browser/FetchHttpStack.js create mode 100644 lib.cjs/browser/FetchHttpStack.js.map create mode 100644 lib.cjs/browser/XHRHttpStack.d.ts create mode 100644 lib.cjs/browser/XHRHttpStack.js create mode 100644 lib.cjs/browser/XHRHttpStack.js.map create mode 100644 lib.cjs/browser/fileSignature.d.ts create mode 100644 lib.cjs/browser/fileSignature.js create mode 100644 lib.cjs/browser/fileSignature.js.map create mode 100644 lib.cjs/browser/index.d.ts create mode 100644 lib.cjs/browser/index.js create mode 100644 lib.cjs/browser/index.js.map create mode 100644 lib.cjs/browser/urlStorage.d.ts create mode 100644 lib.cjs/browser/urlStorage.js create mode 100644 lib.cjs/browser/urlStorage.js.map create mode 100644 lib.cjs/commonFileReader.d.ts create mode 100644 lib.cjs/commonFileReader.js create mode 100644 lib.cjs/commonFileReader.js.map create mode 100644 lib.cjs/cordova/isCordova.d.ts create mode 100644 lib.cjs/cordova/isCordova.js create mode 100644 lib.cjs/cordova/isCordova.js.map create mode 100644 lib.cjs/cordova/readAsByteArray.d.ts create mode 100644 lib.cjs/cordova/readAsByteArray.js create mode 100644 lib.cjs/cordova/readAsByteArray.js.map create mode 100644 lib.cjs/logger.d.ts create mode 100644 lib.cjs/logger.js create mode 100644 lib.cjs/logger.js.map create mode 100644 lib.cjs/node/FileUrlStorage.d.ts create mode 100644 lib.cjs/node/FileUrlStorage.js create mode 100644 lib.cjs/node/FileUrlStorage.js.map create mode 100644 lib.cjs/node/NodeFileReader.d.ts create mode 100644 lib.cjs/node/NodeFileReader.js create mode 100644 lib.cjs/node/NodeFileReader.js.map create mode 100644 lib.cjs/node/NodeHttpStack.d.ts create mode 100644 lib.cjs/node/NodeHttpStack.js create mode 100644 lib.cjs/node/NodeHttpStack.js.map create mode 100644 lib.cjs/node/fileSignature.d.ts create mode 100644 lib.cjs/node/fileSignature.js create mode 100644 lib.cjs/node/fileSignature.js.map create mode 100644 lib.cjs/node/index.d.ts create mode 100644 lib.cjs/node/index.js create mode 100644 lib.cjs/node/index.js.map create mode 100644 lib.cjs/node/sources/NodeStreamFileSource.d.ts create mode 100644 lib.cjs/node/sources/NodeStreamFileSource.js create mode 100644 lib.cjs/node/sources/NodeStreamFileSource.js.map create mode 100644 lib.cjs/node/sources/PathFileSource.d.ts create mode 100644 lib.cjs/node/sources/PathFileSource.js create mode 100644 lib.cjs/node/sources/PathFileSource.js.map create mode 100644 lib.cjs/options.d.ts create mode 100644 lib.cjs/options.js create mode 100644 lib.cjs/options.js.map create mode 100644 lib.cjs/package.json create mode 100644 lib.cjs/reactnative/isReactNative.d.ts create mode 100644 lib.cjs/reactnative/isReactNative.js create mode 100644 lib.cjs/reactnative/isReactNative.js.map create mode 100644 lib.cjs/reactnative/uriToBlob.d.ts create mode 100644 lib.cjs/reactnative/uriToBlob.js create mode 100644 lib.cjs/reactnative/uriToBlob.js.map create mode 100644 lib.cjs/sources/ArrayBufferViewFileSource.d.ts create mode 100644 lib.cjs/sources/ArrayBufferViewFileSource.js create mode 100644 lib.cjs/sources/ArrayBufferViewFileSource.js.map create mode 100644 lib.cjs/sources/BlobFileSource.d.ts create mode 100644 lib.cjs/sources/BlobFileSource.js create mode 100644 lib.cjs/sources/BlobFileSource.js.map create mode 100644 lib.cjs/sources/WebStreamFileSource.d.ts create mode 100644 lib.cjs/sources/WebStreamFileSource.js create mode 100644 lib.cjs/sources/WebStreamFileSource.js.map create mode 100644 lib.cjs/upload.d.ts create mode 100644 lib.cjs/upload.js create mode 100644 lib.cjs/upload.js.map create mode 100644 lib.cjs/uuid.d.ts create mode 100644 lib.cjs/uuid.js create mode 100644 lib.cjs/uuid.js.map create mode 100644 lib.esm/DetailedError.d.ts create mode 100644 lib.esm/DetailedError.js create mode 100644 lib.esm/DetailedError.js.map create mode 100644 lib.esm/NoopUrlStorage.d.ts create mode 100644 lib.esm/NoopUrlStorage.js create mode 100644 lib.esm/NoopUrlStorage.js.map create mode 100644 lib.esm/browser/BrowserFileReader.d.ts create mode 100644 lib.esm/browser/BrowserFileReader.js create mode 100644 lib.esm/browser/BrowserFileReader.js.map create mode 100644 lib.esm/browser/FetchHttpStack.d.ts create mode 100644 lib.esm/browser/FetchHttpStack.js create mode 100644 lib.esm/browser/FetchHttpStack.js.map create mode 100644 lib.esm/browser/XHRHttpStack.d.ts create mode 100644 lib.esm/browser/XHRHttpStack.js create mode 100644 lib.esm/browser/XHRHttpStack.js.map create mode 100644 lib.esm/browser/fileSignature.d.ts create mode 100644 lib.esm/browser/fileSignature.js create mode 100644 lib.esm/browser/fileSignature.js.map create mode 100644 lib.esm/browser/index.d.ts create mode 100644 lib.esm/browser/index.js create mode 100644 lib.esm/browser/index.js.map create mode 100644 lib.esm/browser/urlStorage.d.ts create mode 100644 lib.esm/browser/urlStorage.js create mode 100644 lib.esm/browser/urlStorage.js.map create mode 100644 lib.esm/commonFileReader.d.ts create mode 100644 lib.esm/commonFileReader.js create mode 100644 lib.esm/commonFileReader.js.map create mode 100644 lib.esm/cordova/isCordova.d.ts create mode 100644 lib.esm/cordova/isCordova.js create mode 100644 lib.esm/cordova/isCordova.js.map create mode 100644 lib.esm/cordova/readAsByteArray.d.ts create mode 100644 lib.esm/cordova/readAsByteArray.js create mode 100644 lib.esm/cordova/readAsByteArray.js.map create mode 100644 lib.esm/logger.d.ts create mode 100644 lib.esm/logger.js create mode 100644 lib.esm/logger.js.map create mode 100644 lib.esm/node/FileUrlStorage.d.ts create mode 100644 lib.esm/node/FileUrlStorage.js create mode 100644 lib.esm/node/FileUrlStorage.js.map create mode 100644 lib.esm/node/NodeFileReader.d.ts create mode 100644 lib.esm/node/NodeFileReader.js create mode 100644 lib.esm/node/NodeFileReader.js.map create mode 100644 lib.esm/node/NodeHttpStack.d.ts create mode 100644 lib.esm/node/NodeHttpStack.js create mode 100644 lib.esm/node/NodeHttpStack.js.map create mode 100644 lib.esm/node/fileSignature.d.ts create mode 100644 lib.esm/node/fileSignature.js create mode 100644 lib.esm/node/fileSignature.js.map create mode 100644 lib.esm/node/index.d.ts create mode 100644 lib.esm/node/index.js create mode 100644 lib.esm/node/index.js.map create mode 100644 lib.esm/node/sources/NodeStreamFileSource.d.ts create mode 100644 lib.esm/node/sources/NodeStreamFileSource.js create mode 100644 lib.esm/node/sources/NodeStreamFileSource.js.map create mode 100644 lib.esm/node/sources/PathFileSource.d.ts create mode 100644 lib.esm/node/sources/PathFileSource.js create mode 100644 lib.esm/node/sources/PathFileSource.js.map create mode 100644 lib.esm/options.d.ts create mode 100644 lib.esm/options.js create mode 100644 lib.esm/options.js.map create mode 100644 lib.esm/package.json create mode 100644 lib.esm/reactnative/isReactNative.d.ts create mode 100644 lib.esm/reactnative/isReactNative.js create mode 100644 lib.esm/reactnative/isReactNative.js.map create mode 100644 lib.esm/reactnative/uriToBlob.d.ts create mode 100644 lib.esm/reactnative/uriToBlob.js create mode 100644 lib.esm/reactnative/uriToBlob.js.map create mode 100644 lib.esm/sources/ArrayBufferViewFileSource.d.ts create mode 100644 lib.esm/sources/ArrayBufferViewFileSource.js create mode 100644 lib.esm/sources/ArrayBufferViewFileSource.js.map create mode 100644 lib.esm/sources/BlobFileSource.d.ts create mode 100644 lib.esm/sources/BlobFileSource.js create mode 100644 lib.esm/sources/BlobFileSource.js.map create mode 100644 lib.esm/sources/WebStreamFileSource.d.ts create mode 100644 lib.esm/sources/WebStreamFileSource.js create mode 100644 lib.esm/sources/WebStreamFileSource.js.map create mode 100644 lib.esm/upload.d.ts create mode 100644 lib.esm/upload.js create mode 100644 lib.esm/upload.js.map create mode 100644 lib.esm/uuid.d.ts create mode 100644 lib.esm/uuid.js create mode 100644 lib.esm/uuid.js.map diff --git a/.gitignore b/.gitignore index 1505ea2e..dfe0d2e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ .vscode/settings.json node_modules demos/reactnative/.expo -lib.cjs -lib.esm dist .DS_Store yarn-error.log diff --git a/lib.cjs/DetailedError.d.ts b/lib.cjs/DetailedError.d.ts new file mode 100644 index 00000000..584c9483 --- /dev/null +++ b/lib.cjs/DetailedError.d.ts @@ -0,0 +1,7 @@ +import type { HttpRequest, HttpResponse } from './options.js'; +export declare class DetailedError extends Error { + originalRequest?: HttpRequest; + originalResponse?: HttpResponse; + causingError?: Error; + constructor(message: string, causingErr?: Error, req?: HttpRequest, res?: HttpResponse); +} diff --git a/lib.cjs/DetailedError.js b/lib.cjs/DetailedError.js new file mode 100644 index 00000000..04e76435 --- /dev/null +++ b/lib.cjs/DetailedError.js @@ -0,0 +1,25 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DetailedError = void 0; +class DetailedError extends Error { + constructor(message, causingErr, req, res) { + super(message); + this.originalRequest = req; + this.originalResponse = res; + this.causingError = causingErr; + if (causingErr != null) { + message += `, caused by ${causingErr.toString()}`; + } + if (req != null) { + const requestId = req.getHeader('X-Request-ID') || 'n/a'; + const method = req.getMethod(); + const url = req.getURL(); + const status = res ? res.getStatus() : 'n/a'; + const body = res ? res.getBody() || '' : 'n/a'; + message += `, originated from request (method: ${method}, url: ${url}, response code: ${status}, response text: ${body}, request id: ${requestId})`; + } + this.message = message; + } +} +exports.DetailedError = DetailedError; +//# sourceMappingURL=DetailedError.js.map \ No newline at end of file diff --git a/lib.cjs/DetailedError.js.map b/lib.cjs/DetailedError.js.map new file mode 100644 index 00000000..6ec57b85 --- /dev/null +++ b/lib.cjs/DetailedError.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DetailedError.js","sourceRoot":"","sources":["../lib/DetailedError.ts"],"names":[],"mappings":";;;AAEA,MAAa,aAAc,SAAQ,KAAK;IAOtC,YAAY,OAAe,EAAE,UAAkB,EAAE,GAAiB,EAAE,GAAkB;QACpF,KAAK,CAAC,OAAO,CAAC,CAAA;QAEd,IAAI,CAAC,eAAe,GAAG,GAAG,CAAA;QAC1B,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAA;QAC3B,IAAI,CAAC,YAAY,GAAG,UAAU,CAAA;QAE9B,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACvB,OAAO,IAAI,eAAe,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAA;QACnD,CAAC;QAED,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,KAAK,CAAA;YACxD,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAA;YAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,CAAA;YACxB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;YAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;YAC9C,OAAO,IAAI,sCAAsC,MAAM,UAAU,GAAG,oBAAoB,MAAM,oBAAoB,IAAI,iBAAiB,SAAS,GAAG,CAAA;QACrJ,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;CACF;AA5BD,sCA4BC"} \ No newline at end of file diff --git a/lib.cjs/NoopUrlStorage.d.ts b/lib.cjs/NoopUrlStorage.d.ts new file mode 100644 index 00000000..1329cc56 --- /dev/null +++ b/lib.cjs/NoopUrlStorage.d.ts @@ -0,0 +1,7 @@ +import type { PreviousUpload, UrlStorage } from './options.js'; +export declare class NoopUrlStorage implements UrlStorage { + findAllUploads(): Promise; + findUploadsByFingerprint(_fingerprint: string): Promise; + removeUpload(_urlStorageKey: string): Promise; + addUpload(_urlStorageKey: string, _upload: PreviousUpload): Promise; +} diff --git a/lib.cjs/NoopUrlStorage.js b/lib.cjs/NoopUrlStorage.js new file mode 100644 index 00000000..206c999e --- /dev/null +++ b/lib.cjs/NoopUrlStorage.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NoopUrlStorage = void 0; +class NoopUrlStorage { + findAllUploads() { + return Promise.resolve([]); + } + findUploadsByFingerprint(_fingerprint) { + return Promise.resolve([]); + } + removeUpload(_urlStorageKey) { + return Promise.resolve(); + } + addUpload(_urlStorageKey, _upload) { + return Promise.resolve(undefined); + } +} +exports.NoopUrlStorage = NoopUrlStorage; +//# sourceMappingURL=NoopUrlStorage.js.map \ No newline at end of file diff --git a/lib.cjs/NoopUrlStorage.js.map b/lib.cjs/NoopUrlStorage.js.map new file mode 100644 index 00000000..af898764 --- /dev/null +++ b/lib.cjs/NoopUrlStorage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NoopUrlStorage.js","sourceRoot":"","sources":["../lib/NoopUrlStorage.ts"],"names":[],"mappings":";;;AAEA,MAAa,cAAc;IACzB,cAAc;QACZ,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC;IAED,wBAAwB,CAAC,YAAoB;QAC3C,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC;IAED,YAAY,CAAC,cAAsB;QACjC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;IAC1B,CAAC;IAED,SAAS,CAAC,cAAsB,EAAE,OAAuB;QACvD,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACnC,CAAC;CACF;AAhBD,wCAgBC"} \ No newline at end of file diff --git a/lib.cjs/browser/BrowserFileReader.d.ts b/lib.cjs/browser/BrowserFileReader.d.ts new file mode 100644 index 00000000..155fc24c --- /dev/null +++ b/lib.cjs/browser/BrowserFileReader.d.ts @@ -0,0 +1,4 @@ +import type { FileReader, FileSource, UploadInput } from '../options.js'; +export declare class BrowserFileReader implements FileReader { + openFile(input: UploadInput, chunkSize: number): Promise; +} diff --git a/lib.cjs/browser/BrowserFileReader.js b/lib.cjs/browser/BrowserFileReader.js new file mode 100644 index 00000000..efbe880c --- /dev/null +++ b/lib.cjs/browser/BrowserFileReader.js @@ -0,0 +1,33 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BrowserFileReader = void 0; +const isReactNative_js_1 = require("../reactnative/isReactNative.js"); +const uriToBlob_js_1 = require("../reactnative/uriToBlob.js"); +const commonFileReader_js_1 = require("../commonFileReader.js"); +const BlobFileSource_js_1 = require("../sources/BlobFileSource.js"); +class BrowserFileReader { + 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 ((0, isReactNative_js_1.isReactNativeFile)(input)) { + if (!(0, isReactNative_js_1.isReactNativePlatform)()) { + throw new Error('tus: file objects with `uri` property is only supported in React Native'); + } + try { + const blob = await (0, uriToBlob_js_1.uriToBlob)(input.uri); + return new BlobFileSource_js_1.BlobFileSource(blob); + } + catch (err) { + throw new Error(`tus: cannot fetch \`file.uri\` as Blob, make sure the uri is correct and accessible. ${err}`); + } + } + const fileSource = (0, commonFileReader_js_1.openFile)(input, chunkSize); + if (fileSource) + return fileSource; + throw new Error(`in this environment the source object may only be an instance of: ${commonFileReader_js_1.supportedTypes.join(', ')}`); + } +} +exports.BrowserFileReader = BrowserFileReader; +//# sourceMappingURL=BrowserFileReader.js.map \ No newline at end of file diff --git a/lib.cjs/browser/BrowserFileReader.js.map b/lib.cjs/browser/BrowserFileReader.js.map new file mode 100644 index 00000000..3aa5be02 --- /dev/null +++ b/lib.cjs/browser/BrowserFileReader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"BrowserFileReader.js","sourceRoot":"","sources":["../../lib/browser/BrowserFileReader.ts"],"names":[],"mappings":";;;AAAA,sEAA0F;AAC1F,8DAAuD;AAEvD,gEAG+B;AAE/B,oEAA6D;AAE7D,MAAa,iBAAiB;IAC5B,KAAK,CAAC,QAAQ,CAAC,KAAkB,EAAE,SAAiB;QAClD,wEAAwE;QACxE,qEAAqE;QACrE,2DAA2D;QAC3D,4CAA4C;QAC5C,IAAI,IAAA,oCAAiB,EAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,IAAA,wCAAqB,GAAE,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAA;YAC5F,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,IAAA,wBAAS,EAAC,KAAK,CAAC,GAAG,CAAC,CAAA;gBACvC,OAAO,IAAI,kCAAc,CAAC,IAAI,CAAC,CAAA;YACjC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACb,wFAAwF,GAAG,EAAE,CAC9F,CAAA;YACH,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,IAAA,8BAAY,EAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QACjD,IAAI,UAAU;YAAE,OAAO,UAAU,CAAA;QAEjC,MAAM,IAAI,KAAK,CACb,qEAAqE,oCAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACrG,CAAA;IACH,CAAC;CACF;AA5BD,8CA4BC"} \ No newline at end of file diff --git a/lib.cjs/browser/FetchHttpStack.d.ts b/lib.cjs/browser/FetchHttpStack.d.ts new file mode 100644 index 00000000..7f77e9b9 --- /dev/null +++ b/lib.cjs/browser/FetchHttpStack.d.ts @@ -0,0 +1,30 @@ +import type { HttpProgressHandler, HttpRequest, HttpResponse, HttpStack, SliceType } from '../options.js'; +export declare class FetchHttpStack implements HttpStack { + createRequest(method: string, url: string): FetchRequest; + getName(): string; +} +declare class FetchRequest implements HttpRequest { + private _method; + private _url; + private _headers; + private _controller; + constructor(method: string, url: string); + getMethod(): string; + getURL(): string; + setHeader(header: string, value: string): void; + getHeader(header: string): string; + setProgressHandler(_progressHandler: HttpProgressHandler): void; + send(body?: SliceType): Promise; + abort(): Promise; + getUnderlyingObject(): undefined; +} +declare class FetchResponse implements HttpResponse { + private _res; + private _body; + constructor(res: Response, body: string); + getStatus(): number; + getHeader(header: string): string | undefined; + getBody(): string; + getUnderlyingObject(): Response; +} +export {}; diff --git a/lib.cjs/browser/FetchHttpStack.js b/lib.cjs/browser/FetchHttpStack.js new file mode 100644 index 00000000..691cc6d4 --- /dev/null +++ b/lib.cjs/browser/FetchHttpStack.js @@ -0,0 +1,77 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FetchHttpStack = void 0; +const is_stream_1 = require("is-stream"); +// TODO: Add tests for this. +class FetchHttpStack { + createRequest(method, url) { + return new FetchRequest(method, url); + } + getName() { + return 'FetchHttpStack'; + } +} +exports.FetchHttpStack = FetchHttpStack; +class FetchRequest { + constructor(method, url) { + this._headers = {}; + this._controller = new AbortController(); + this._method = method; + this._url = url; + } + getMethod() { + return this._method; + } + getURL() { + return this._url; + } + setHeader(header, value) { + this._headers[header] = value; + } + getHeader(header) { + return this._headers[header]; + } + setProgressHandler(_progressHandler) { + // The Fetch API currently does not expose a way to track upload progress. + } + async send(body) { + if ((0, is_stream_1.readable)(body)) { + throw new Error('Using a Node.js readable stream as HTTP request body is not supported using the Fetch API HTTP stack.'); + } + const res = await fetch(this._url, { + method: this._method, + headers: this._headers, + body, + signal: this._controller.signal, + }); + const resBody = await res.text(); + return new FetchResponse(res, resBody); + } + abort() { + this._controller.abort(); + return Promise.resolve(); + } + getUnderlyingObject() { + // In the Fetch API, there is no object representing the request. + return undefined; + } +} +class FetchResponse { + constructor(res, body) { + this._res = res; + this._body = body; + } + getStatus() { + return this._res.status; + } + getHeader(header) { + return this._res.headers.get(header) || undefined; + } + getBody() { + return this._body; + } + getUnderlyingObject() { + return this._res; + } +} +//# sourceMappingURL=FetchHttpStack.js.map \ No newline at end of file diff --git a/lib.cjs/browser/FetchHttpStack.js.map b/lib.cjs/browser/FetchHttpStack.js.map new file mode 100644 index 00000000..bd18ab4b --- /dev/null +++ b/lib.cjs/browser/FetchHttpStack.js.map @@ -0,0 +1 @@ +{"version":3,"file":"FetchHttpStack.js","sourceRoot":"","sources":["../../lib/browser/FetchHttpStack.ts"],"names":[],"mappings":";;;AAAA,yCAA4D;AAS5D,4BAA4B;AAC5B,MAAa,cAAc;IACzB,aAAa,CAAC,MAAc,EAAE,GAAW;QACvC,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,CAAC;IAED,OAAO;QACL,OAAO,gBAAgB,CAAA;IACzB,CAAC;CACF;AARD,wCAQC;AAED,MAAM,YAAY;IAMhB,YAAY,MAAc,EAAE,GAAW;QAH/B,aAAQ,GAA2B,EAAE,CAAA;QACrC,gBAAW,GAAG,IAAI,eAAe,EAAE,CAAA;QAGzC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAA;IACjB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,SAAS,CAAC,MAAc,EAAE,KAAa;QACrC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,KAAK,CAAA;IAC/B,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9B,CAAC;IAED,kBAAkB,CAAC,gBAAqC;QACtD,0EAA0E;IAC5E,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAgB;QACzB,IAAI,IAAA,oBAAoB,EAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CACb,uGAAuG,CACxG,CAAA;QACH,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;YACjC,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,IAAI;YACJ,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM;SAChC,CAAC,CAAA;QAEF,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAChC,OAAO,IAAI,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACxC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAA;QACxB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;IAC1B,CAAC;IAED,mBAAmB;QACjB,iEAAiE;QACjE,OAAO,SAAS,CAAA;IAClB,CAAC;CACF;AAED,MAAM,aAAa;IAIjB,YAAY,GAAa,EAAE,IAAY;QACrC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAA;QACf,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAA;IACzB,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS,CAAA;IACnD,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,mBAAmB;QACjB,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;CACF"} \ No newline at end of file diff --git a/lib.cjs/browser/XHRHttpStack.d.ts b/lib.cjs/browser/XHRHttpStack.d.ts new file mode 100644 index 00000000..59e59ed0 --- /dev/null +++ b/lib.cjs/browser/XHRHttpStack.d.ts @@ -0,0 +1,5 @@ +import type { HttpRequest, HttpStack } from '../options.js'; +export declare class XHRHttpStack implements HttpStack { + createRequest(method: string, url: string): HttpRequest; + getName(): string; +} diff --git a/lib.cjs/browser/XHRHttpStack.js b/lib.cjs/browser/XHRHttpStack.js new file mode 100644 index 00000000..c2e52729 --- /dev/null +++ b/lib.cjs/browser/XHRHttpStack.js @@ -0,0 +1,86 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.XHRHttpStack = void 0; +const is_stream_1 = require("is-stream"); +class XHRHttpStack { + createRequest(method, url) { + return new XHRRequest(method, url); + } + getName() { + return 'XHRHttpStack'; + } +} +exports.XHRHttpStack = XHRHttpStack; +class XHRRequest { + constructor(method, url) { + this._xhr = new XMLHttpRequest(); + this._headers = {}; + this._xhr.open(method, url, true); + this._method = method; + this._url = url; + } + 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) { + if ((0, is_stream_1.readable)(body)) { + throw new Error('Using a Node.js readable stream as HTTP request body is not supported using the XMLHttpRequest HTTP stack.'); + } + return new Promise((resolve, reject) => { + this._xhr.onload = () => { + resolve(new XHRResponse(this._xhr)); + }; + this._xhr.onerror = (err) => { + reject(err); + }; + this._xhr.send(body); + }); + } + abort() { + this._xhr.abort(); + return Promise.resolve(); + } + getUnderlyingObject() { + return this._xhr; + } +} +class XHRResponse { + constructor(xhr) { + this._xhr = xhr; + } + getStatus() { + return this._xhr.status; + } + getHeader(header) { + return this._xhr.getResponseHeader(header) || undefined; + } + getBody() { + return this._xhr.responseText; + } + getUnderlyingObject() { + return this._xhr; + } +} +//# sourceMappingURL=XHRHttpStack.js.map \ No newline at end of file diff --git a/lib.cjs/browser/XHRHttpStack.js.map b/lib.cjs/browser/XHRHttpStack.js.map new file mode 100644 index 00000000..cfa36310 --- /dev/null +++ b/lib.cjs/browser/XHRHttpStack.js.map @@ -0,0 +1 @@ +{"version":3,"file":"XHRHttpStack.js","sourceRoot":"","sources":["../../lib/browser/XHRHttpStack.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.cjs/browser/fileSignature.d.ts b/lib.cjs/browser/fileSignature.d.ts new file mode 100644 index 00000000..36d136c1 --- /dev/null +++ b/lib.cjs/browser/fileSignature.d.ts @@ -0,0 +1,5 @@ +import type { UploadInput, UploadOptions } from '../options.js'; +/** + * Generate a fingerprint for a file which will be used the store the endpoint + */ +export declare function fingerprint(file: UploadInput, options: UploadOptions): Promise | Promise; diff --git a/lib.cjs/browser/fileSignature.js b/lib.cjs/browser/fileSignature.js new file mode 100644 index 00000000..3f28a4c4 --- /dev/null +++ b/lib.cjs/browser/fileSignature.js @@ -0,0 +1,37 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fingerprint = fingerprint; +const isReactNative_js_1 = require("../reactnative/isReactNative.js"); +/** + * Generate a fingerprint for a file which will be used the store the endpoint + */ +function fingerprint(file, options) { + if ((0, isReactNative_js_1.isReactNativePlatform)() && (0, isReactNative_js_1.isReactNativeFile)(file)) { + return Promise.resolve(reactNativeFingerprint(file, options)); + } + if (file instanceof Blob) { + return Promise.resolve( + //@ts-expect-error TODO: We have to check the input type here + // This can be fixed by moving the fingerprint function to the FileReader class + ['tus-br', file.name, file.type, file.size, file.lastModified, options.endpoint].join('-')); + } + return Promise.resolve(null); +} +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; +} +//# sourceMappingURL=fileSignature.js.map \ No newline at end of file diff --git a/lib.cjs/browser/fileSignature.js.map b/lib.cjs/browser/fileSignature.js.map new file mode 100644 index 00000000..34c488a4 --- /dev/null +++ b/lib.cjs/browser/fileSignature.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fileSignature.js","sourceRoot":"","sources":["../../lib/browser/fileSignature.ts"],"names":[],"mappings":";;AAMA,kCAcC;AAnBD,sEAA0F;AAE1F;;GAEG;AACH,SAAgB,WAAW,CAAC,IAAiB,EAAE,OAAsB;IACnE,IAAI,IAAA,wCAAqB,GAAE,IAAI,IAAA,oCAAiB,EAAC,IAAI,CAAC,EAAE,CAAC;QACvD,OAAO,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IAC/D,CAAC;IAED,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,OAAO;QACpB,6DAA6D;QAC7D,+EAA+E;QAC/E,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAC3F,CAAA;IACH,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAC9B,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAqB,EAAE,OAAsB;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IAC3E,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAC9F,GAAG,CACJ,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,kDAAkD;IAClD,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;QAChC,IAAI,IAAI,IAAI,CAAA,CAAC,2BAA2B;IAC1C,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"} \ No newline at end of file diff --git a/lib.cjs/browser/index.d.ts b/lib.cjs/browser/index.d.ts new file mode 100644 index 00000000..cc4b180f --- /dev/null +++ b/lib.cjs/browser/index.d.ts @@ -0,0 +1,47 @@ +import { DetailedError } from '../DetailedError.js'; +import { NoopUrlStorage } from '../NoopUrlStorage.js'; +import { enableDebugLog } from '../logger.js'; +import type { UploadInput, UploadOptions } from '../options.js'; +import { BaseUpload } from '../upload.js'; +import { BrowserFileReader } from './BrowserFileReader.js'; +import { XHRHttpStack as DefaultHttpStack } from './XHRHttpStack.js'; +import { fingerprint } from './fileSignature.js'; +import { WebStorageUrlStorage, canStoreURLs } from './urlStorage.js'; +declare const defaultOptions: { + httpStack: DefaultHttpStack; + fileReader: BrowserFileReader; + urlStorage: NoopUrlStorage | WebStorageUrlStorage; + fingerprint: typeof fingerprint; + endpoint: undefined; + uploadUrl: undefined; + metadata: {}; + metadataForPartialUploads: {}; + uploadSize: undefined; + onProgress: undefined; + onChunkComplete: undefined; + onSuccess: undefined; + onError: undefined; + onUploadUrlAvailable: undefined; + overridePatchMethod: boolean; + headers: {}; + addRequestId: boolean; + onBeforeRequest: undefined; + onAfterResponse: undefined; + onShouldRetry: (err: DetailedError) => boolean; + chunkSize: number; + retryDelays: number[]; + parallelUploads: number; + parallelUploadBoundaries: undefined; + storeFingerprintForResuming: boolean; + removeFingerprintOnSuccess: boolean; + uploadLengthDeferred: boolean; + uploadDataDuringCreation: boolean; + protocol: UploadOptions["protocol"]; +}; +declare class Upload extends BaseUpload { + constructor(file: UploadInput, options?: Partial); + static terminate(url: string, options?: Partial): Promise; +} +declare const isSupported: boolean; +export { Upload, defaultOptions, isSupported, canStoreURLs, enableDebugLog, DetailedError }; +export type * from '../options.js'; diff --git a/lib.cjs/browser/index.js b/lib.cjs/browser/index.js new file mode 100644 index 00000000..b1a3b80e --- /dev/null +++ b/lib.cjs/browser/index.js @@ -0,0 +1,39 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DetailedError = exports.enableDebugLog = exports.canStoreURLs = exports.isSupported = exports.defaultOptions = exports.Upload = void 0; +const DetailedError_js_1 = require("../DetailedError.js"); +Object.defineProperty(exports, "DetailedError", { enumerable: true, get: function () { return DetailedError_js_1.DetailedError; } }); +const NoopUrlStorage_js_1 = require("../NoopUrlStorage.js"); +const logger_js_1 = require("../logger.js"); +Object.defineProperty(exports, "enableDebugLog", { enumerable: true, get: function () { return logger_js_1.enableDebugLog; } }); +const upload_js_1 = require("../upload.js"); +const BrowserFileReader_js_1 = require("./BrowserFileReader.js"); +const XHRHttpStack_js_1 = require("./XHRHttpStack.js"); +const fileSignature_js_1 = require("./fileSignature.js"); +const urlStorage_js_1 = require("./urlStorage.js"); +Object.defineProperty(exports, "canStoreURLs", { enumerable: true, get: function () { return urlStorage_js_1.canStoreURLs; } }); +const defaultOptions = { + ...upload_js_1.defaultOptions, + httpStack: new XHRHttpStack_js_1.XHRHttpStack(), + fileReader: new BrowserFileReader_js_1.BrowserFileReader(), + urlStorage: urlStorage_js_1.canStoreURLs ? new urlStorage_js_1.WebStorageUrlStorage() : new NoopUrlStorage_js_1.NoopUrlStorage(), + fingerprint: fileSignature_js_1.fingerprint, +}; +exports.defaultOptions = defaultOptions; +class Upload extends upload_js_1.BaseUpload { + constructor(file, options = {}) { + const allOpts = { ...defaultOptions, ...options }; + super(file, allOpts); + } + static terminate(url, options = {}) { + const allOpts = { ...defaultOptions, ...options }; + return (0, upload_js_1.terminate)(url, allOpts); + } +} +exports.Upload = Upload; +// 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'; +exports.isSupported = isSupported; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib.cjs/browser/index.js.map b/lib.cjs/browser/index.js.map new file mode 100644 index 00000000..15733a8d --- /dev/null +++ b/lib.cjs/browser/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/browser/index.ts"],"names":[],"mappings":";;;AAAA,0DAAmD;AAuCyB,8FAvCnE,gCAAa,OAuCmE;AAtCzF,4DAAqD;AACrD,4CAA6C;AAqCe,+FArCnD,0BAAc,OAqCmD;AAnC1E,4CAA0F;AAE1F,iEAA0D;AAC1D,uDAAoE;AACpE,yDAAgD;AAChD,mDAAoE;AA8BtB,6FA9Bf,4BAAY,OA8Be;AA5B1D,MAAM,cAAc,GAAG;IACrB,GAAG,0BAAkB;IACrB,SAAS,EAAE,IAAI,8BAAgB,EAAE;IACjC,UAAU,EAAE,IAAI,wCAAiB,EAAE;IACnC,UAAU,EAAE,4BAAY,CAAC,CAAC,CAAC,IAAI,oCAAoB,EAAE,CAAC,CAAC,CAAC,IAAI,kCAAc,EAAE;IAC5E,WAAW,EAAX,8BAAW;CACZ,CAAA;AAsBgB,wCAAc;AApB/B,MAAM,MAAO,SAAQ,sBAAU;IAC7B,YAAY,IAAiB,EAAE,UAAkC,EAAE;QACjE,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAA;QACjD,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACtB,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,GAAW,EAAE,UAAkC,EAAE;QAChE,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAA;QACjD,OAAO,IAAA,qBAAS,EAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChC,CAAC;CACF;AAUQ,wBAAM;AARf,qGAAqG;AACrG,MAAM,WAAW,GACf,OAAO,cAAc,KAAK,UAAU;IACpC,OAAO,IAAI,KAAK,UAAU;IAC1B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,UAAU,CAAA;AAIX,kCAAW"} \ No newline at end of file diff --git a/lib.cjs/browser/urlStorage.d.ts b/lib.cjs/browser/urlStorage.d.ts new file mode 100644 index 00000000..8714c2cf --- /dev/null +++ b/lib.cjs/browser/urlStorage.d.ts @@ -0,0 +1,9 @@ +import type { PreviousUpload, UrlStorage } from '../options.js'; +export declare const canStoreURLs: boolean; +export declare class WebStorageUrlStorage implements UrlStorage { + findAllUploads(): Promise; + findUploadsByFingerprint(fingerprint: string): Promise; + removeUpload(urlStorageKey: string): Promise; + addUpload(fingerprint: string, upload: PreviousUpload): Promise; + private _findEntries; +} diff --git a/lib.cjs/browser/urlStorage.js b/lib.cjs/browser/urlStorage.js new file mode 100644 index 00000000..8694adde --- /dev/null +++ b/lib.cjs/browser/urlStorage.js @@ -0,0 +1,79 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WebStorageUrlStorage = exports.canStoreURLs = void 0; +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, String(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) + // TODO: Replace `code` with `name` + if (e instanceof DOMException && (e.code === e.SECURITY_ERR || e.code === e.QUOTA_EXCEEDED_ERR)) { + hasStorage = false; + } + else { + throw e; + } +} +exports.canStoreURLs = hasStorage; +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 == null) { + throw new Error(`didn't find key for item ${i}`); + } + // Ignore entires that are not from tus-js-client + if (key.indexOf(prefix) !== 0) + continue; + const item = localStorage.getItem(key); + if (item == null) { + throw new Error(`didn't find item for key ${key}`); + } + try { + // TODO: Validate JSON + const upload = JSON.parse(item); + 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; + } +} +exports.WebStorageUrlStorage = WebStorageUrlStorage; +//# sourceMappingURL=urlStorage.js.map \ No newline at end of file diff --git a/lib.cjs/browser/urlStorage.js.map b/lib.cjs/browser/urlStorage.js.map new file mode 100644 index 00000000..aaa5b04a --- /dev/null +++ b/lib.cjs/browser/urlStorage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"urlStorage.js","sourceRoot":"","sources":["../../lib/browser/urlStorage.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.cjs/commonFileReader.d.ts b/lib.cjs/commonFileReader.d.ts new file mode 100644 index 00000000..884e582e --- /dev/null +++ b/lib.cjs/commonFileReader.d.ts @@ -0,0 +1,7 @@ +import type { FileSource, UploadInput } from './options.js'; +/** + * openFile provides FileSources for input types that have to be handled in all environments, + * including Node.js and browsers. + */ +export declare function openFile(input: UploadInput, chunkSize: number): FileSource | null; +export declare const supportedTypes: string[]; diff --git a/lib.cjs/commonFileReader.js b/lib.cjs/commonFileReader.js new file mode 100644 index 00000000..f74e318b --- /dev/null +++ b/lib.cjs/commonFileReader.js @@ -0,0 +1,50 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.supportedTypes = void 0; +exports.openFile = openFile; +const ArrayBufferViewFileSource_js_1 = require("./sources/ArrayBufferViewFileSource.js"); +const BlobFileSource_js_1 = require("./sources/BlobFileSource.js"); +const WebStreamFileSource_js_1 = require("./sources/WebStreamFileSource.js"); +/** + * openFile provides FileSources for input types that have to be handled in all environments, + * including Node.js and browsers. + */ +function openFile(input, chunkSize) { + // File is a subtype of Blob, so we only check for Blob here. + // Note: We could turn Blobs into ArrayBuffers using `input.arrayBuffer()` and then + // pass it to the ArrayBufferFileSource. However, in browsers, a File instance can + // represent a file on disk. By keeping it a File instance and passing it to XHR/Fetch, + // we can avoid reading the entire file into memory. + if (input instanceof Blob) { + return new BlobFileSource_js_1.BlobFileSource(input); + } + // ArrayBufferViews can be TypedArray (e.g. Uint8Array) or DataView instances. + // Note that Node.js' Buffers are also Uint8Arrays. + if (ArrayBuffer.isView(input)) { + return new ArrayBufferViewFileSource_js_1.ArrayBufferViewFileSource(input); + } + // SharedArrayBuffer is not available in all browser context for security reasons. + // Hence we check if the constructor exists at all. + if (input instanceof ArrayBuffer || + (typeof SharedArrayBuffer !== 'undefined' && input instanceof SharedArrayBuffer)) { + const view = new DataView(input); + return new ArrayBufferViewFileSource_js_1.ArrayBufferViewFileSource(view); + } + if (input instanceof ReadableStream) { + chunkSize = Number(chunkSize); + if (!Number.isFinite(chunkSize)) { + throw new Error('cannot create source for stream without a finite value for the `chunkSize` option'); + } + return new WebStreamFileSource_js_1.WebStreamFileSource(input); + } + return null; +} +exports.supportedTypes = [ + 'File', + 'Blob', + 'ArrayBuffer', + 'SharedArrayBuffer', + 'ArrayBufferView', + 'ReadableStream (Web Streams)', +]; +//# sourceMappingURL=commonFileReader.js.map \ No newline at end of file diff --git a/lib.cjs/commonFileReader.js.map b/lib.cjs/commonFileReader.js.map new file mode 100644 index 00000000..38b228f4 --- /dev/null +++ b/lib.cjs/commonFileReader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commonFileReader.js","sourceRoot":"","sources":["../lib/commonFileReader.ts"],"names":[],"mappings":";;;AASA,4BAsCC;AA9CD,yFAAkF;AAClF,mEAA4D;AAC5D,6EAAsE;AAEtE;;;GAGG;AACH,SAAgB,QAAQ,CAAC,KAAkB,EAAE,SAAiB;IAC5D,6DAA6D;IAC7D,mFAAmF;IACnF,kFAAkF;IAClF,uFAAuF;IACvF,oDAAoD;IACpD,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QAC1B,OAAO,IAAI,kCAAc,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,8EAA8E;IAC9E,mDAAmD;IACnD,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,wDAAyB,CAAC,KAAK,CAAC,CAAA;IAC7C,CAAC;IAED,kFAAkF;IAClF,mDAAmD;IACnD,IACE,KAAK,YAAY,WAAW;QAC5B,CAAC,OAAO,iBAAiB,KAAK,WAAW,IAAI,KAAK,YAAY,iBAAiB,CAAC,EAChF,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAA;QAChC,OAAO,IAAI,wDAAyB,CAAC,IAAI,CAAC,CAAA;IAC5C,CAAC;IAED,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;QACpC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,mFAAmF,CACpF,CAAA;QACH,CAAC;QAED,OAAO,IAAI,4CAAmB,CAAC,KAAK,CAAC,CAAA;IACvC,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAEY,QAAA,cAAc,GAAG;IAC5B,MAAM;IACN,MAAM;IACN,aAAa;IACb,mBAAmB;IACnB,iBAAiB;IACjB,8BAA8B;CAC/B,CAAA"} \ No newline at end of file diff --git a/lib.cjs/cordova/isCordova.d.ts b/lib.cjs/cordova/isCordova.d.ts new file mode 100644 index 00000000..711d1d31 --- /dev/null +++ b/lib.cjs/cordova/isCordova.d.ts @@ -0,0 +1,2 @@ +declare const isCordova: () => boolean; +export { isCordova }; diff --git a/lib.cjs/cordova/isCordova.js b/lib.cjs/cordova/isCordova.js new file mode 100644 index 00000000..7fd650ca --- /dev/null +++ b/lib.cjs/cordova/isCordova.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isCordova = void 0; +const isCordova = () => typeof window !== 'undefined' && + ('PhoneGap' in window || 'Cordova' in window || 'cordova' in window); +exports.isCordova = isCordova; +//# sourceMappingURL=isCordova.js.map \ No newline at end of file diff --git a/lib.cjs/cordova/isCordova.js.map b/lib.cjs/cordova/isCordova.js.map new file mode 100644 index 00000000..38b1cab6 --- /dev/null +++ b/lib.cjs/cordova/isCordova.js.map @@ -0,0 +1 @@ +{"version":3,"file":"isCordova.js","sourceRoot":"","sources":["../../lib/cordova/isCordova.ts"],"names":[],"mappings":";;;AAAA,MAAM,SAAS,GAAG,GAAG,EAAE,CACrB,OAAO,MAAM,KAAK,WAAW;IAC7B,CAAC,UAAU,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,CAAC,CAAA;AAE7D,8BAAS"} \ No newline at end of file diff --git a/lib.cjs/cordova/readAsByteArray.d.ts b/lib.cjs/cordova/readAsByteArray.d.ts new file mode 100644 index 00000000..e89ab2c4 --- /dev/null +++ b/lib.cjs/cordova/readAsByteArray.d.ts @@ -0,0 +1,6 @@ +/** + * readAsByteArray converts a File/Blob 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 declare function readAsByteArray(chunk: Blob): Promise; diff --git a/lib.cjs/cordova/readAsByteArray.js b/lib.cjs/cordova/readAsByteArray.js new file mode 100644 index 00000000..d5a8172d --- /dev/null +++ b/lib.cjs/cordova/readAsByteArray.js @@ -0,0 +1,28 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.readAsByteArray = readAsByteArray; +/** + * readAsByteArray converts a File/Blob 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 + */ +// TODO: Reconsider whether this is a sensible approach or whether we cause +// high memory usage with `chunkSize` is unset. +function readAsByteArray(chunk) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + if (!(reader.result instanceof ArrayBuffer)) { + reject(new Error(`invalid result types for readAsArrayBuffer: ${typeof reader.result}`)); + return; + } + const value = new Uint8Array(reader.result); + resolve(value); + }; + reader.onerror = (err) => { + reject(err); + }; + reader.readAsArrayBuffer(chunk); + }); +} +//# sourceMappingURL=readAsByteArray.js.map \ No newline at end of file diff --git a/lib.cjs/cordova/readAsByteArray.js.map b/lib.cjs/cordova/readAsByteArray.js.map new file mode 100644 index 00000000..287d15c8 --- /dev/null +++ b/lib.cjs/cordova/readAsByteArray.js.map @@ -0,0 +1 @@ +{"version":3,"file":"readAsByteArray.js","sourceRoot":"","sources":["../../lib/cordova/readAsByteArray.ts"],"names":[],"mappings":";;AAOA,0CAgBC;AAvBD;;;;GAIG;AACH,2EAA2E;AAC3E,+CAA+C;AAC/C,SAAgB,eAAe,CAAC,KAAW;IACzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAA;QAC/B,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;YACnB,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,YAAY,WAAW,CAAC,EAAE,CAAC;gBAC5C,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,OAAO,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;gBACxF,OAAM;YACR,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC3C,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC,CAAA;QACD,MAAM,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,EAAE;YACvB,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC,CAAA;QACD,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;AACJ,CAAC"} \ No newline at end of file diff --git a/lib.cjs/logger.d.ts b/lib.cjs/logger.d.ts new file mode 100644 index 00000000..f6d7e036 --- /dev/null +++ b/lib.cjs/logger.d.ts @@ -0,0 +1,2 @@ +export declare function enableDebugLog(): void; +export declare function log(msg: string): void; diff --git a/lib.cjs/logger.js b/lib.cjs/logger.js new file mode 100644 index 00000000..c86a0606 --- /dev/null +++ b/lib.cjs/logger.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.enableDebugLog = enableDebugLog; +exports.log = log; +let isEnabled = false; +// TODO: Replace this global state with an option for the Upload class +function enableDebugLog() { + isEnabled = true; +} +function log(msg) { + if (!isEnabled) + return; + console.log(msg); +} +//# sourceMappingURL=logger.js.map \ No newline at end of file diff --git a/lib.cjs/logger.js.map b/lib.cjs/logger.js.map new file mode 100644 index 00000000..c9e7dac6 --- /dev/null +++ b/lib.cjs/logger.js.map @@ -0,0 +1 @@ +{"version":3,"file":"logger.js","sourceRoot":"","sources":["../lib/logger.ts"],"names":[],"mappings":";;AAGA,wCAEC;AAED,kBAGC;AAVD,IAAI,SAAS,GAAG,KAAK,CAAA;AAErB,sEAAsE;AACtE,SAAgB,cAAc;IAC5B,SAAS,GAAG,IAAI,CAAA;AAClB,CAAC;AAED,SAAgB,GAAG,CAAC,GAAW;IAC7B,IAAI,CAAC,SAAS;QAAE,OAAM;IACtB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AAClB,CAAC"} \ No newline at end of file diff --git a/lib.cjs/node/FileUrlStorage.d.ts b/lib.cjs/node/FileUrlStorage.d.ts new file mode 100644 index 00000000..c22126f5 --- /dev/null +++ b/lib.cjs/node/FileUrlStorage.d.ts @@ -0,0 +1,16 @@ +import type { PreviousUpload, UrlStorage } from '../options.js'; +export declare const canStoreURLs = true; +export declare class FileUrlStorage implements UrlStorage { + path: string; + constructor(filePath: string); + findAllUploads(): Promise; + findUploadsByFingerprint(fingerprint: string): Promise; + removeUpload(urlStorageKey: string): Promise; + addUpload(fingerprint: string, upload: PreviousUpload): Promise; + private _setItem; + private _getItems; + private _removeItem; + private _lockfileOptions; + private _writeData; + private _getData; +} diff --git a/lib.cjs/node/FileUrlStorage.js b/lib.cjs/node/FileUrlStorage.js new file mode 100644 index 00000000..43f3ca2d --- /dev/null +++ b/lib.cjs/node/FileUrlStorage.js @@ -0,0 +1,90 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FileUrlStorage = exports.canStoreURLs = void 0; +const promises_1 = require("node:fs/promises"); +const proper_lockfile_1 = require("proper-lockfile"); +exports.canStoreURLs = true; +class FileUrlStorage { + constructor(filePath) { + this.path = filePath; + } + async findAllUploads() { + return await this._getItems('tus::'); + } + async findUploadsByFingerprint(fingerprint) { + return await this._getItems(`tus::${fingerprint}`); + } + async removeUpload(urlStorageKey) { + await this._removeItem(urlStorageKey); + } + async addUpload(fingerprint, upload) { + const id = Math.round(Math.random() * 1e12); + const key = `tus::${fingerprint}::${id}`; + await this._setItem(key, upload); + return key; + } + async _setItem(key, value) { + const release = await (0, proper_lockfile_1.lock)(this.path, this._lockfileOptions()); + try { + const data = await this._getData(); + data[key] = value; + await this._writeData(data); + } + finally { + await release(); + } + } + async _getItems(prefix) { + const data = await this._getData(); + const results = Object.keys(data) + .filter((key) => key.startsWith(prefix)) + .map((key) => { + const obj = data[key]; + obj.urlStorageKey = key; + return obj; + }); + return results; + } + async _removeItem(key) { + const release = await (0, proper_lockfile_1.lock)(this.path, this._lockfileOptions()); + try { + const data = await this._getData(); + delete data[key]; + await this._writeData(data); + } + finally { + await release(); + } + } + _lockfileOptions() { + return { + realpath: false, + retries: { + retries: 5, + minTimeout: 20, + }, + }; + } + async _writeData(data) { + await (0, promises_1.writeFile)(this.path, JSON.stringify(data), { + encoding: 'utf8', + mode: 0o660, + flag: 'w', + }); + } + async _getData() { + let data = ''; + try { + data = await (0, promises_1.readFile)(this.path, 'utf8'); + } + catch (err) { + // return empty data if file does not exist + if (err != null && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') + return {}; + } + data = data.trim(); + return data.length === 0 ? {} : JSON.parse(data); + } +} +exports.FileUrlStorage = FileUrlStorage; +//# sourceMappingURL=FileUrlStorage.js.map \ No newline at end of file diff --git a/lib.cjs/node/FileUrlStorage.js.map b/lib.cjs/node/FileUrlStorage.js.map new file mode 100644 index 00000000..9700cb1c --- /dev/null +++ b/lib.cjs/node/FileUrlStorage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"FileUrlStorage.js","sourceRoot":"","sources":["../../lib/node/FileUrlStorage.ts"],"names":[],"mappings":";;;AAAA,+CAAsD;AACtD,qDAAsC;AAGzB,QAAA,YAAY,GAAG,IAAI,CAAA;AAEhC,MAAa,cAAc;IAGzB,YAAY,QAAgB;QAC1B,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACtC,CAAC;IAED,KAAK,CAAC,wBAAwB,CAAC,WAAmB;QAChD,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,WAAW,EAAE,CAAC,CAAA;IACpD,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,aAAqB;QACtC,MAAM,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,CAAA;IACvC,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,WAAmB,EAAE,MAAsB;QACzD,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,QAAQ,WAAW,KAAK,EAAE,EAAE,CAAA;QAExC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAChC,OAAO,GAAG,CAAA;IACZ,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,GAAW,EAAE,KAAc;QAChD,MAAM,OAAO,GAAG,MAAM,IAAA,sBAAI,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAA;QAE9D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAA;YAClC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;YACjB,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAC7B,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,EAAE,CAAA;QACjB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,MAAc;QACpC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAA;QAElC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;aAC9B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;aACvC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;YACrB,GAAG,CAAC,aAAa,GAAG,GAAG,CAAA;YACvB,OAAO,GAAG,CAAA;QACZ,CAAC,CAAC,CAAA;QAEJ,OAAO,OAAO,CAAA;IAChB,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,GAAW;QACnC,MAAM,OAAO,GAAG,MAAM,IAAA,sBAAI,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAA;QAE9D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAA;YAClC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAA;YAChB,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAC7B,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,EAAE,CAAA;QACjB,CAAC;IACH,CAAC;IAEO,gBAAgB;QACtB,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE;gBACP,OAAO,EAAE,CAAC;gBACV,UAAU,EAAE,EAAE;aACf;SACF,CAAA;IACH,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,IAAa;QACpC,MAAM,IAAA,oBAAS,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;YAC/C,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,GAAG;SACV,CAAC,CAAA;IACJ,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,IAAI,IAAI,GAAG,EAAE,CAAA;QACb,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAA,mBAAQ,EAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,2CAA2C;YAC3C,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;gBAClF,OAAO,EAAE,CAAA;QACb,CAAC;QAED,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAElB,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC;CACF;AAjGD,wCAiGC"} \ No newline at end of file diff --git a/lib.cjs/node/NodeFileReader.d.ts b/lib.cjs/node/NodeFileReader.d.ts new file mode 100644 index 00000000..e0914cdb --- /dev/null +++ b/lib.cjs/node/NodeFileReader.d.ts @@ -0,0 +1,4 @@ +import type { FileReader, UploadInput } from '../options.js'; +export declare class NodeFileReader implements FileReader { + openFile(input: UploadInput, chunkSize: number): Promise | Promise; +} diff --git a/lib.cjs/node/NodeFileReader.js b/lib.cjs/node/NodeFileReader.js new file mode 100644 index 00000000..eedf2fc8 --- /dev/null +++ b/lib.cjs/node/NodeFileReader.js @@ -0,0 +1,52 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NodeFileReader = void 0; +const node_fs_1 = require("node:fs"); +const is_stream_1 = __importDefault(require("is-stream")); +const commonFileReader_js_1 = require("../commonFileReader.js"); +const NodeStreamFileSource_js_1 = require("./sources/NodeStreamFileSource.js"); +const PathFileSource_js_1 = require("./sources/PathFileSource.js"); +function isPathReference(input) { + return (typeof input === 'object' && + input !== null && + 'path' in input && + (typeof input.path === 'string' || Buffer.isBuffer(input.path))); +} +class NodeFileReader { + openFile(input, chunkSize) { + if (isPathReference(input)) { + return (0, PathFileSource_js_1.getFileSourceFromPath)(input); + } + if (is_stream_1.default.readable(input)) { + chunkSize = Number(chunkSize); + if (!Number.isFinite(chunkSize)) { + throw new Error('cannot create source for stream without a finite value for the `chunkSize` option; specify a chunkSize to control the memory consumption'); + } + return Promise.resolve(new NodeStreamFileSource_js_1.NodeStreamFileSource(input)); + } + const fileSource = (0, commonFileReader_js_1.openFile)(input, chunkSize); + if (fileSource) + return Promise.resolve(fileSource); + throw new Error(`in this environment the source object may only be an instance of: ${commonFileReader_js_1.supportedTypes.join(', ')}, fs.ReadStream (Node.js), stream.Readable (Node.js)`); + } +} +exports.NodeFileReader = NodeFileReader; +/** + * This (unused) function is a simple test to ensure that fs.ReadStreams + * satisfy the PathReference interface. In the past, tus-js-client explicitly + * accepted fs.ReadStreams and included it in its type definitions. + * + * Since tus-js-client v5, we have moved away from only accepting fs.ReadStream + * in favor of a more generic PathReference. This function ensures that the definition + * of PathReference includes fs.ReadStream. If this wasn't the case, the TypeScript + * compiler would complain during the build step, making this a poor-man's type test. + */ +// biome-ignore lint/correctness/noUnusedVariables: see above +function testFsReadStreamAsPathReference() { + const pathReference = (0, node_fs_1.createReadStream)('test.txt'); + new NodeFileReader().openFile(pathReference, 1024); +} +//# sourceMappingURL=NodeFileReader.js.map \ No newline at end of file diff --git a/lib.cjs/node/NodeFileReader.js.map b/lib.cjs/node/NodeFileReader.js.map new file mode 100644 index 00000000..907eb198 --- /dev/null +++ b/lib.cjs/node/NodeFileReader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NodeFileReader.js","sourceRoot":"","sources":["../../lib/node/NodeFileReader.ts"],"names":[],"mappings":";;;;;;AAAA,qCAA0C;AAC1C,0DAAgC;AAEhC,gEAG+B;AAE/B,+EAAwE;AACxE,mEAAmE;AAEnE,SAAS,eAAe,CAAC,KAAkB;IACzC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAChE,CAAA;AACH,CAAC;AAED,MAAa,cAAc;IACzB,QAAQ,CAAC,KAAkB,EAAE,SAAiB;QAC5C,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,IAAA,yCAAqB,EAAC,KAAK,CAAC,CAAA;QACrC,CAAC;QAED,IAAI,mBAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;YAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CACb,0IAA0I,CAC3I,CAAA;YACH,CAAC;YACD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,8CAAoB,CAAC,KAAK,CAAC,CAAC,CAAA;QACzD,CAAC;QAED,MAAM,UAAU,GAAG,IAAA,8BAAY,EAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QACjD,IAAI,UAAU;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAElD,MAAM,IAAI,KAAK,CACb,qEAAqE,oCAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,sDAAsD,CACzJ,CAAA;IACH,CAAC;CACF;AAvBD,wCAuBC;AAED;;;;;;;;;GASG;AACH,6DAA6D;AAC7D,SAAS,+BAA+B;IACtC,MAAM,aAAa,GAAkB,IAAA,0BAAgB,EAAC,UAAU,CAAC,CAAA;IACjE,IAAI,cAAc,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;AACpD,CAAC"} \ No newline at end of file diff --git a/lib.cjs/node/NodeHttpStack.d.ts b/lib.cjs/node/NodeHttpStack.d.ts new file mode 100644 index 00000000..55a3744c --- /dev/null +++ b/lib.cjs/node/NodeHttpStack.d.ts @@ -0,0 +1,26 @@ +import * as http from 'node:http'; +import type { HttpProgressHandler, HttpRequest, HttpResponse, HttpStack, SliceType } from '../options.js'; +export declare class NodeHttpStack implements HttpStack { + private _requestOptions; + constructor(requestOptions?: http.RequestOptions); + createRequest(method: string, url: string): Request; + getName(): string; +} +declare class Request implements HttpRequest { + private _method; + private _url; + private _headers; + private _request; + private _progressHandler; + private _requestOptions; + constructor(method: string, url: string, options: http.RequestOptions); + getMethod(): string; + getURL(): string; + setHeader(header: string, value: string): void; + getHeader(header: string): string; + setProgressHandler(progressHandler: HttpProgressHandler): void; + send(body?: SliceType): Promise; + abort(): Promise; + getUnderlyingObject(): http.ClientRequest | null; +} +export {}; diff --git a/lib.cjs/node/NodeHttpStack.js b/lib.cjs/node/NodeHttpStack.js new file mode 100644 index 00000000..21ebf3ca --- /dev/null +++ b/lib.cjs/node/NodeHttpStack.js @@ -0,0 +1,259 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NodeHttpStack = void 0; +// The url.parse method is superseeded by the url.URL constructor, +// but it is still included in Node.js +const http = __importStar(require("node:http")); +const https = __importStar(require("node:https")); +const node_stream_1 = require("node:stream"); +const node_url_1 = require("node:url"); +const is_stream_1 = __importDefault(require("is-stream")); +const lodash_throttle_1 = __importDefault(require("lodash.throttle")); +class NodeHttpStack { + constructor(requestOptions = {}) { + this._requestOptions = requestOptions; + } + createRequest(method, url) { + return new Request(method, url, this._requestOptions); + } + getName() { + return 'NodeHttpStack'; + } +} +exports.NodeHttpStack = NodeHttpStack; +class Request { + constructor(method, url, options) { + this._headers = {}; + this._request = null; + this._progressHandler = () => { }; + this._method = method; + this._url = url; + this._requestOptions = options; + } + getMethod() { + return this._method; + } + getURL() { + return this._url; + } + setHeader(header, value) { + this._headers[header] = value; + } + getHeader(header) { + return this._headers[header]; + } + setProgressHandler(progressHandler) { + this._progressHandler = progressHandler; + } + async send(body) { + var _a; + let nodeBody; + if (body != null) { + if (body instanceof Blob) { + nodeBody = new Uint8Array(await body.arrayBuffer()); + } + else if (body instanceof Uint8Array) { + nodeBody = body; + } + else if (ArrayBuffer.isView(body)) { + // Any typed array other than Uint8Array or a DataVew + nodeBody = new Uint8Array(body.buffer, body.byteOffset, body.byteLength); + } + else if (is_stream_1.default.readable(body)) { + nodeBody = body; + } + else { + throw new Error( + // @ts-expect-error According to the types, this case cannot happen. But + // we still want to try logging the constructor if this code is reached by accident. + `Unsupported HTTP request body type in Node.js HTTP stack: ${typeof body} (constructor: ${(_a = body === null || body === void 0 ? void 0 : body.constructor) === null || _a === void 0 ? void 0 : _a.name})`); + } + } + return new Promise((resolve, reject) => { + const options = { + ...(0, node_url_1.parse)(this._url), + ...this._requestOptions, + method: this._method, + headers: { + ...(this._requestOptions.headers || {}), + ...this._headers, + }, + }; + // TODO: What to do here? + // @ts-expect-error We still have to type `size` for `body` + if (body === null || body === void 0 ? void 0 : body.size) { + // @ts-expect-error We still have to type `size` for `body` + options.headers['Content-Length'] = body.size; + } + const httpModule = options.protocol === 'https:' ? https : http; + this._request = httpModule.request(options); + const req = this._request; + req.on('response', (res) => { + const resChunks = []; + res.on('data', (data) => { + resChunks.push(data); + }); + res.on('end', () => { + const responseText = Buffer.concat(resChunks).toString('utf8'); + resolve(new Response(res, responseText)); + }); + }); + req.on('error', (err) => { + reject(err); + }); + if (nodeBody instanceof node_stream_1.Readable) { + // Readable stream are piped through a PassThrough instance, which + // counts the number of bytes passed through. This is used, for example, + // when an fs.ReadStream is provided to tus-js-client. + nodeBody.pipe(new ProgressEmitter(this._progressHandler)).pipe(req); + } + else if (nodeBody instanceof Uint8Array) { + // For Buffers and Uint8Arrays (in Node.js all buffers are instances of Uint8Array), + // we write chunks of the buffer to the stream and use that to track the progress. + // This is used when either a Buffer or a normal readable stream is provided + // to tus-js-client. + writeBufferToStreamWithProgress(req, nodeBody, this._progressHandler); + } + else { + req.end(); + } + }); + } + abort() { + if (this._request != null) + this._request.abort(); + return Promise.resolve(); + } + getUnderlyingObject() { + return this._request; + } +} +class Response { + constructor(res, body) { + this._response = res; + this._body = body; + } + getStatus() { + if (this._response.statusCode === undefined) { + throw new Error('no status code available yet'); + } + return this._response.statusCode; + } + getHeader(header) { + const values = this._response.headers[header.toLowerCase()]; + if (Array.isArray(values)) { + return values.join(', '); + } + return values; + } + getBody() { + return this._body; + } + getUnderlyingObject() { + return this._response; + } +} +// ProgressEmitter is a simple PassThrough-style transform stream which keeps +// track of the number of bytes which have been piped through it and will +// invoke the `onprogress` function whenever new number are available. +class ProgressEmitter extends node_stream_1.Transform { + constructor(onprogress) { + super(); + this._position = 0; + // The _onprogress property will be invoked, whenever a chunk is piped + // through this transformer. Since chunks are usually quite small (64kb), + // these calls can occur frequently, especially when you have a good + // connection to the remote server. Therefore, we are throtteling them to + // prevent accessive function calls. + this._onprogress = (0, lodash_throttle_1.default)(onprogress, 100, { + leading: true, + trailing: false, + }); + } + _transform(chunk, _encoding, callback) { + this._position += chunk.length; + this._onprogress(this._position); + callback(null, chunk); + } +} +// writeBufferToStreamWithProgress writes chunks from `source` (either a +// Buffer or Uint8Array) to the readable stream `stream`. +// The size of the chunk depends on the stream's highWaterMark to fill the +// stream's internal buffer as best as possible. +// If the internal buffer is full, the callback `onprogress` will be invoked +// to notify about the write progress. Writing will be resumed once the internal +// buffer is empty, as indicated by the emitted `drain` event. +// See https://nodejs.org/docs/latest/api/stream.html#buffering for more details +// on the buffering behavior of streams. +function writeBufferToStreamWithProgress(stream, source, onprogress) { + onprogress = (0, lodash_throttle_1.default)(onprogress, 100, { + leading: true, + trailing: false, + }); + let offset = 0; + function writeNextChunk() { + // Take at most the amount of bytes from highWaterMark. This should fill the streams + // internal buffer already. + const chunkSize = Math.min(stream.writableHighWaterMark, source.length - offset); + // Note: We use subarray instead of slice because it works without copying data for + // Buffers and Uint8Arrays. + const chunk = source.subarray(offset, offset + chunkSize); + offset += chunk.length; + // `write` returns true if the internal buffer is not full and we should write more. + // If the stream is destroyed because the request is aborted, it will return false + // and no 'drain' event is emitted, so won't continue writing data. + const canContinue = stream.write(chunk); + if (!canContinue) { + // If the buffer is full, wait for the 'drain' event to write more data. + stream.once('drain', writeNextChunk); + onprogress(offset); + } + else if (offset < source.length) { + // If there's still data to write and the buffer is not full, write next chunk. + writeNextChunk(); + } + else { + // If all data has been written, close the stream if needed, and emit a 'finish' event. + stream.end(); + } + } + // Start writing the first chunk. + writeNextChunk(); +} +//# sourceMappingURL=NodeHttpStack.js.map \ No newline at end of file diff --git a/lib.cjs/node/NodeHttpStack.js.map b/lib.cjs/node/NodeHttpStack.js.map new file mode 100644 index 00000000..4843d874 --- /dev/null +++ b/lib.cjs/node/NodeHttpStack.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NodeHttpStack.js","sourceRoot":"","sources":["../../lib/node/NodeHttpStack.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.cjs/node/fileSignature.d.ts b/lib.cjs/node/fileSignature.d.ts new file mode 100644 index 00000000..3019c80b --- /dev/null +++ b/lib.cjs/node/fileSignature.d.ts @@ -0,0 +1,2 @@ +import type { UploadInput, UploadOptions } from '../options.js'; +export declare function fingerprint(file: UploadInput, options: UploadOptions): Promise; diff --git a/lib.cjs/node/fileSignature.js b/lib.cjs/node/fileSignature.js new file mode 100644 index 00000000..b5012e7f --- /dev/null +++ b/lib.cjs/node/fileSignature.js @@ -0,0 +1,59 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fingerprint = fingerprint; +const node_crypto_1 = require("node:crypto"); +const node_fs_1 = require("node:fs"); +const promises_1 = require("node:fs/promises"); +const path = __importStar(require("node:path")); +async function fingerprint(file, options) { + if (Buffer.isBuffer(file)) { + // create MD5 hash for buffer type + const blockSize = 64 * 1024; // 64kb + const content = file.slice(0, Math.min(blockSize, file.length)); + const hash = (0, node_crypto_1.createHash)('md5').update(content).digest('hex'); + const ret = ['node-buffer', hash, file.length, options.endpoint].join('-'); + return ret; + } + if (file instanceof node_fs_1.ReadStream && file.path != null) { + const name = path.resolve(Buffer.isBuffer(file.path) ? file.path.toString('utf-8') : file.path); + const info = await (0, promises_1.stat)(file.path); + const ret = ['node-file', name, info.size, info.mtime.getTime(), options.endpoint].join('-'); + return ret; + } + // fingerprint cannot be computed for file input type + return null; +} +//# sourceMappingURL=fileSignature.js.map \ No newline at end of file diff --git a/lib.cjs/node/fileSignature.js.map b/lib.cjs/node/fileSignature.js.map new file mode 100644 index 00000000..da12a2fd --- /dev/null +++ b/lib.cjs/node/fileSignature.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fileSignature.js","sourceRoot":"","sources":["../../lib/node/fileSignature.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,kCAuBC;AA7BD,6CAAwC;AACxC,qCAAoC;AACpC,+CAAuC;AACvC,gDAAiC;AAG1B,KAAK,UAAU,WAAW,CAC/B,IAAiB,EACjB,OAAsB;IAEtB,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,kCAAkC;QAClC,MAAM,SAAS,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,OAAO;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;QAC/D,MAAM,IAAI,GAAG,IAAA,wBAAU,EAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC5D,MAAM,GAAG,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC1E,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,IAAI,IAAI,YAAY,oBAAU,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/F,MAAM,IAAI,GAAG,MAAM,IAAA,eAAI,EAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClC,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAE5F,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,qDAAqD;IACrD,OAAO,IAAI,CAAA;AACb,CAAC"} \ No newline at end of file diff --git a/lib.cjs/node/index.d.ts b/lib.cjs/node/index.d.ts new file mode 100644 index 00000000..df45263c --- /dev/null +++ b/lib.cjs/node/index.d.ts @@ -0,0 +1,47 @@ +import { DetailedError } from '../DetailedError.js'; +import { NoopUrlStorage } from '../NoopUrlStorage.js'; +import { enableDebugLog } from '../logger.js'; +import type { UploadInput, UploadOptions } from '../options.js'; +import { BaseUpload } from '../upload.js'; +import { canStoreURLs } from './FileUrlStorage.js'; +import { NodeFileReader } from './NodeFileReader.js'; +import { NodeHttpStack as DefaultHttpStack } from './NodeHttpStack.js'; +import { fingerprint } from './fileSignature.js'; +declare const defaultOptions: { + httpStack: DefaultHttpStack; + fileReader: NodeFileReader; + urlStorage: NoopUrlStorage; + fingerprint: typeof fingerprint; + endpoint: undefined; + uploadUrl: undefined; + metadata: {}; + metadataForPartialUploads: {}; + uploadSize: undefined; + onProgress: undefined; + onChunkComplete: undefined; + onSuccess: undefined; + onError: undefined; + onUploadUrlAvailable: undefined; + overridePatchMethod: boolean; + headers: {}; + addRequestId: boolean; + onBeforeRequest: undefined; + onAfterResponse: undefined; + onShouldRetry: (err: DetailedError) => boolean; + chunkSize: number; + retryDelays: number[]; + parallelUploads: number; + parallelUploadBoundaries: undefined; + storeFingerprintForResuming: boolean; + removeFingerprintOnSuccess: boolean; + uploadLengthDeferred: boolean; + uploadDataDuringCreation: boolean; + protocol: UploadOptions["protocol"]; +}; +declare class Upload extends BaseUpload { + constructor(file: UploadInput, options?: Partial); + static terminate(url: string, options?: Partial): Promise; +} +declare const isSupported = true; +export { Upload, defaultOptions, isSupported, canStoreURLs, enableDebugLog, DetailedError }; +export type * from '../options.js'; diff --git a/lib.cjs/node/index.js b/lib.cjs/node/index.js new file mode 100644 index 00000000..79f4973d --- /dev/null +++ b/lib.cjs/node/index.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DetailedError = exports.enableDebugLog = exports.canStoreURLs = exports.isSupported = exports.defaultOptions = exports.Upload = void 0; +const DetailedError_js_1 = require("../DetailedError.js"); +Object.defineProperty(exports, "DetailedError", { enumerable: true, get: function () { return DetailedError_js_1.DetailedError; } }); +const NoopUrlStorage_js_1 = require("../NoopUrlStorage.js"); +const logger_js_1 = require("../logger.js"); +Object.defineProperty(exports, "enableDebugLog", { enumerable: true, get: function () { return logger_js_1.enableDebugLog; } }); +const upload_js_1 = require("../upload.js"); +const FileUrlStorage_js_1 = require("./FileUrlStorage.js"); +Object.defineProperty(exports, "canStoreURLs", { enumerable: true, get: function () { return FileUrlStorage_js_1.canStoreURLs; } }); +const NodeFileReader_js_1 = require("./NodeFileReader.js"); +const NodeHttpStack_js_1 = require("./NodeHttpStack.js"); +const fileSignature_js_1 = require("./fileSignature.js"); +const defaultOptions = { + ...upload_js_1.defaultOptions, + httpStack: new NodeHttpStack_js_1.NodeHttpStack(), + fileReader: new NodeFileReader_js_1.NodeFileReader(), + urlStorage: new NoopUrlStorage_js_1.NoopUrlStorage(), + fingerprint: fileSignature_js_1.fingerprint, +}; +exports.defaultOptions = defaultOptions; +class Upload extends upload_js_1.BaseUpload { + constructor(file, options = {}) { + const allOpts = { ...defaultOptions, ...options }; + super(file, allOpts); + } + static terminate(url, options = {}) { + const allOpts = { ...defaultOptions, ...options }; + return (0, upload_js_1.terminate)(url, allOpts); + } +} +exports.Upload = Upload; +// The Node.js environment does not have restrictions which may cause +// tus-js-client not to function. +const isSupported = true; +exports.isSupported = isSupported; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib.cjs/node/index.js.map b/lib.cjs/node/index.js.map new file mode 100644 index 00000000..e9d518de --- /dev/null +++ b/lib.cjs/node/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/node/index.ts"],"names":[],"mappings":";;;AAAA,0DAAmD;AAqCyB,8FArCnE,gCAAa,OAqCmE;AApCzF,4DAAqD;AACrD,4CAA6C;AAmCe,+FAnCnD,0BAAc,OAmCmD;AAjC1E,4CAA0F;AAE1F,2DAAkD;AA+BJ,6FA/BrC,gCAAY,OA+BqC;AA9B1D,2DAAoD;AACpD,yDAAsE;AACtE,yDAAgD;AAEhD,MAAM,cAAc,GAAG;IACrB,GAAG,0BAAkB;IACrB,SAAS,EAAE,IAAI,gCAAgB,EAAE;IACjC,UAAU,EAAE,IAAI,kCAAc,EAAE;IAChC,UAAU,EAAE,IAAI,kCAAc,EAAE;IAChC,WAAW,EAAX,8BAAW;CACZ,CAAA;AAoBgB,wCAAc;AAlB/B,MAAM,MAAO,SAAQ,sBAAU;IAC7B,YAAY,IAAiB,EAAE,UAAkC,EAAE;QACjE,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAA;QACjD,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACtB,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,GAAW,EAAE,UAAkC,EAAE;QAChE,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAA;QACjD,OAAO,IAAA,qBAAS,EAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChC,CAAC;CACF;AAQQ,wBAAM;AANf,qEAAqE;AACrE,iCAAiC;AACjC,MAAM,WAAW,GAAG,IAAI,CAAA;AAIS,kCAAW"} \ No newline at end of file diff --git a/lib.cjs/node/sources/NodeStreamFileSource.d.ts b/lib.cjs/node/sources/NodeStreamFileSource.d.ts new file mode 100644 index 00000000..639aa110 --- /dev/null +++ b/lib.cjs/node/sources/NodeStreamFileSource.d.ts @@ -0,0 +1,29 @@ +import type { Readable } from 'node:stream'; +import type { FileSource } from '../../options.js'; +/** + * StreamSource provides an interface to obtain slices of a Readable stream for + * various ranges. + * It will buffer read data, to allow for following pattern: + * - Call slice(startA, endA) will buffer the data of the requested range + * - Call slice(startB, endB) will return data from the buffer if startA <= startB <= endA. + * If endB > endA, it will also consume new data from the stream. + * Note that it is forbidden to call with startB < startA or startB > endA. In other words, + * the slice calls cannot seek back and must not skip data from the stream. + */ +export declare class NodeStreamFileSource implements FileSource { + size: null; + private _stream; + private _buf; + private _bufPos; + private _ended; + private _error; + constructor(stream: Readable); + slice(start: number, end: number): Promise<{ + value: Buffer & { + size?: number; + }; + size: number; + done: boolean; + }>; + close(): void; +} diff --git a/lib.cjs/node/sources/NodeStreamFileSource.js b/lib.cjs/node/sources/NodeStreamFileSource.js new file mode 100644 index 00000000..8e371ba7 --- /dev/null +++ b/lib.cjs/node/sources/NodeStreamFileSource.js @@ -0,0 +1,120 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NodeStreamFileSource = void 0; +/** + * readChunk reads a chunk with the given size from the given + * stream. It will wait until enough data is available to satisfy + * the size requirement before resolving. + * Only if the stream ends, the function may resolve with a buffer + * smaller than the size argument. + * Note that we rely on the stream behaving as Node.js documents: + * https://nodejs.org/api/stream.html#readablereadsize + */ +async function readChunk(stream, size) { + return new Promise((resolve, reject) => { + const onError = (err) => { + cleanup(); + reject(err); + }; + const onReadable = () => { + // TODO: Node requires size to be less than 1GB. Add a validation for that + const chunk = stream.read(size); + if (chunk != null) { + cleanup(); + resolve(chunk); + } + }; + const onEnd = () => { + cleanup(); + resolve(Buffer.alloc(0)); + }; + const cleanup = () => { + stream.off('error', onError); + stream.off('readable', onReadable); + stream.off('end', onEnd); + }; + stream.once('error', onError); + stream.on('readable', onReadable); + stream.once('end', onEnd); + }); +} +/** + * StreamSource provides an interface to obtain slices of a Readable stream for + * various ranges. + * It will buffer read data, to allow for following pattern: + * - Call slice(startA, endA) will buffer the data of the requested range + * - Call slice(startB, endB) will return data from the buffer if startA <= startB <= endA. + * If endB > endA, it will also consume new data from the stream. + * Note that it is forbidden to call with startB < startA or startB > endA. In other words, + * the slice calls cannot seek back and must not skip data from the stream. + */ +// TODO: Consider converting the node stream in a web stream. Then we can share the stream +// handling between browsers and node.js. +class NodeStreamFileSource { + constructor(stream) { + // Setting the size to null indicates that we have no calculation available + // for how much data this stream will emit requiring the user to specify + // it manually (see the `uploadSize` option). + this.size = null; + this._buf = Buffer.alloc(0); + this._bufPos = 0; + this._ended = false; + this._error = null; + this._stream = stream; + stream.pause(); + stream.on('end', () => { + this._ended = true; + }); + stream.on('error', (err) => { + this._error = err; + }); + } + async slice(start, end) { + // Fail fast if the caller requests a proportion of the data which is not + // available any more. + if (start < this._bufPos) { + throw new Error('cannot slice from position which we already seeked away'); + } + if (start > this._bufPos + this._buf.length) { + throw new Error('slice start is outside of buffer (currently not implemented)'); + } + if (this._error) { + throw this._error; + } + let returnBuffer; + // Always attempt to drain the buffer first, even if this means that we + // return less data than the caller requested. + if (start < this._bufPos + this._buf.length) { + const bufStart = start - this._bufPos; + const bufEnd = Math.min(this._buf.length, end - this._bufPos); + returnBuffer = this._buf.slice(bufStart, bufEnd); + } + else { + returnBuffer = Buffer.alloc(0); + } + // If the stream has ended already, read calls would not finish, so return early here. + if (this._ended) { + const size = returnBuffer.length; + return { value: returnBuffer, size, done: true }; + } + // If we could not satisfy the slice request from the buffer only, read more data from + // the stream and add it to the buffer. + const requestedSize = end - start; + if (requestedSize > returnBuffer.length) { + // Note: We assume that the stream returns not more than the requested size. + const newChunk = await readChunk(this._stream, requestedSize - returnBuffer.length); + // Append the new chunk to the buffer + returnBuffer = Buffer.concat([returnBuffer, newChunk]); + } + // Important: Store the read data, so consecutive slice calls can access the same data. + this._buf = returnBuffer; + this._bufPos = start; + const size = returnBuffer.length; + return { value: returnBuffer, size, done: this._ended }; + } + close() { + this._stream.destroy(); + } +} +exports.NodeStreamFileSource = NodeStreamFileSource; +//# sourceMappingURL=NodeStreamFileSource.js.map \ No newline at end of file diff --git a/lib.cjs/node/sources/NodeStreamFileSource.js.map b/lib.cjs/node/sources/NodeStreamFileSource.js.map new file mode 100644 index 00000000..2f1db69d --- /dev/null +++ b/lib.cjs/node/sources/NodeStreamFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NodeStreamFileSource.js","sourceRoot":"","sources":["../../../lib/node/sources/NodeStreamFileSource.ts"],"names":[],"mappings":";;;AAGA;;;;;;;;GAQG;AACH,KAAK,UAAU,SAAS,CAAC,MAAgB,EAAE,IAAY;IACrD,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,OAAO,GAAG,CAAC,GAAU,EAAE,EAAE;YAC7B,OAAO,EAAE,CAAA;YACT,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC,CAAA;QAED,MAAM,UAAU,GAAG,GAAG,EAAE;YACtB,0EAA0E;YAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAE/B,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,OAAO,EAAE,CAAA;gBACT,OAAO,CAAC,KAAK,CAAC,CAAA;YAChB,CAAC;QACH,CAAC,CAAA;QAED,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,OAAO,EAAE,CAAA;YACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAC1B,CAAC,CAAA;QAED,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC5B,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;YAClC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAC1B,CAAC,CAAA;QAED,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC7B,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;QACjC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,0FAA0F;AAC1F,yCAAyC;AACzC,MAAa,oBAAoB;IAgB/B,YAAY,MAAgB;QAf5B,2EAA2E;QAC3E,wEAAwE;QACxE,6CAA6C;QAC7C,SAAI,GAAG,IAAI,CAAA;QAIH,SAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAEtB,YAAO,GAAG,CAAC,CAAA;QAEX,WAAM,GAAG,KAAK,CAAA;QAEd,WAAM,GAAiB,IAAI,CAAA;QAGjC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QAErB,MAAM,CAAC,KAAK,EAAE,CAAA;QACd,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACpB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;QACpB,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,IAAI,CAAC,MAAM,GAAG,GAAG,CAAA;QACnB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,GAAW;QACpC,yEAAyE;QACzE,sBAAsB;QACtB,IAAI,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;QAC5E,CAAC;QAED,IAAI,KAAK,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAA;QACjF,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,MAAM,CAAA;QACnB,CAAC;QAED,IAAI,YAAwC,CAAA;QAC5C,uEAAuE;QACvE,8CAA8C;QAC9C,IAAI,KAAK,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5C,MAAM,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC,OAAO,CAAA;YACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAA;YAE7D,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAClD,CAAC;aAAM,CAAC;YACN,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAChC,CAAC;QAED,sFAAsF;QACtF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAA;YAChC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;QAClD,CAAC;QAED,sFAAsF;QACtF,uCAAuC;QACvC,MAAM,aAAa,GAAG,GAAG,GAAG,KAAK,CAAA;QACjC,IAAI,aAAa,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC;YACxC,4EAA4E;YAC5E,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;YAEnF,qCAAqC;YACrC,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAA;QACxD,CAAC;QAED,uFAAuF;QACvF,IAAI,CAAC,IAAI,GAAG,YAAY,CAAA;QACxB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QAEpB,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAA;QAChC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,CAAA;IACzD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;IACxB,CAAC;CACF;AAnFD,oDAmFC"} \ No newline at end of file diff --git a/lib.cjs/node/sources/PathFileSource.d.ts b/lib.cjs/node/sources/PathFileSource.d.ts new file mode 100644 index 00000000..cf22ec4a --- /dev/null +++ b/lib.cjs/node/sources/PathFileSource.d.ts @@ -0,0 +1,17 @@ +import { type ReadStream } from 'node:fs'; +import type { FileSource, PathReference } from '../../options.js'; +export declare function getFileSourceFromPath(file: PathReference): Promise; +export declare class PathFileSource implements FileSource { + size: number; + private _file; + private _path; + constructor(file: PathReference, path: string, size: number); + slice(start: number, end: number): Promise<{ + value: ReadStream & { + size?: number; + }; + size: number; + done: boolean; + }>; + close(): void; +} diff --git a/lib.cjs/node/sources/PathFileSource.js b/lib.cjs/node/sources/PathFileSource.js new file mode 100644 index 00000000..1b6ead13 --- /dev/null +++ b/lib.cjs/node/sources/PathFileSource.js @@ -0,0 +1,61 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PathFileSource = void 0; +exports.getFileSourceFromPath = getFileSourceFromPath; +const node_fs_1 = require("node:fs"); +async function getFileSourceFromPath(file) { + var _a; + const path = file.path.toString(); + const { size } = await node_fs_1.promises.stat(path); + // The path reference might be configured to not read from the beginning + // to the end, but instead from a slice in between. In this case, we consider + // that range to indicate the actual uploadable size. + // This happens, for example, if a path reference is used with the `parallelUploads` + // option. There, the path reference is sliced into multiple fs.ReadStreams to fit the + // number of `parallelUploads`. Each ReadStream has `start` and `end` set. + // Note: `stream.end` is Infinity by default, so we need the check `isFinite`. + // Note: `stream.end` is treated inclusively, so we need to add 1 here. + // See the comment in slice() for more context. + const start = (_a = file.start) !== null && _a !== void 0 ? _a : 0; + const end = file.end != null && Number.isFinite(file.end) ? file.end + 1 : size; + const actualSize = end - start; + return new PathFileSource(file, path, actualSize); +} +class PathFileSource { + constructor(file, path, size) { + this._file = file; + this._path = path; + this.size = size; + } + slice(start, end) { + var _a; + // TODO: Does this create multiple file descriptors? Can we reduce this by + // using a file handle instead? + // The path reference might be configured to not read from the beginning, + // but instead start at a different offset. The start value from the caller + // does not include the offset, so we need to add this offset to our range later. + // This happens, for example, if a path reference is used with the `parallelUploads` + // option. First, the path reference is sliced into multiple fs.ReadStreams to fit the + // number of `parallelUploads`. Each ReadStream has `start` set. + const offset = (_a = this._file.start) !== null && _a !== void 0 ? _a : 0; + const stream = (0, node_fs_1.createReadStream)(this._path, { + start: offset + start, + // The `end` option for createReadStream is treated inclusively + // (see https://nodejs.org/api/fs.html#fs_fs_createreadstream_path_options). + // However, the Buffer#slice(start, end) and also our Source#slice(start, end) + // method treat the end range exclusively, so we have to subtract 1. + // This prevents an off-by-one error when reporting upload progress. + end: offset + end - 1, + autoClose: true, + }); + const size = Math.min(end - start, this.size); + const done = size >= this.size; + return Promise.resolve({ value: stream, size, done }); + } + close() { + // TODO: Ensure that the read streams are closed + // TODO: Previously, the passed fs.ReadStream was closed here. Should we keep this behavior? If not, this is a breaking change. + } +} +exports.PathFileSource = PathFileSource; +//# sourceMappingURL=PathFileSource.js.map \ No newline at end of file diff --git a/lib.cjs/node/sources/PathFileSource.js.map b/lib.cjs/node/sources/PathFileSource.js.map new file mode 100644 index 00000000..25ed23b6 --- /dev/null +++ b/lib.cjs/node/sources/PathFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"PathFileSource.js","sourceRoot":"","sources":["../../../lib/node/sources/PathFileSource.ts"],"names":[],"mappings":";;;AAGA,sDAkBC;AArBD,qCAAmF;AAG5E,KAAK,UAAU,qBAAqB,CAAC,IAAmB;;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,kBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAE5C,wEAAwE;IACxE,6EAA6E;IAC7E,qDAAqD;IACrD,oFAAoF;IACpF,sFAAsF;IACtF,0EAA0E;IAC1E,8EAA8E;IAC9E,uEAAuE;IACvE,+CAA+C;IAC/C,MAAM,KAAK,GAAG,MAAA,IAAI,CAAC,KAAK,mCAAI,CAAC,CAAA;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC/E,MAAM,UAAU,GAAG,GAAG,GAAG,KAAK,CAAA;IAE9B,OAAO,IAAI,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAA;AACnD,CAAC;AAED,MAAa,cAAc;IAOzB,YAAY,IAAmB,EAAE,IAAY,EAAE,IAAY;QACzD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAED,KAAK,CAAC,KAAa,EAAE,GAAW;;QAC9B,0EAA0E;QAC1E,+BAA+B;QAC/B,yEAAyE;QACzE,2EAA2E;QAC3E,iFAAiF;QACjF,oFAAoF;QACpF,sFAAsF;QACtF,gEAAgE;QAChE,MAAM,MAAM,GAAG,MAAA,IAAI,CAAC,KAAK,CAAC,KAAK,mCAAI,CAAC,CAAA;QAEpC,MAAM,MAAM,GAAmC,IAAA,0BAAgB,EAAC,IAAI,CAAC,KAAK,EAAE;YAC1E,KAAK,EAAE,MAAM,GAAG,KAAK;YACrB,+DAA+D;YAC/D,4EAA4E;YAC5E,8EAA8E;YAC9E,oEAAoE;YACpE,oEAAoE;YACpE,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,CAAC;YACrB,SAAS,EAAE,IAAI;SAChB,CAAC,CAAA;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAC7C,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAA;QAC9B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IACvD,CAAC;IAED,KAAK;QACH,gDAAgD;QAChD,+HAA+H;IACjI,CAAC;CACF;AA3CD,wCA2CC"} \ No newline at end of file diff --git a/lib.cjs/options.d.ts b/lib.cjs/options.d.ts new file mode 100644 index 00000000..c6801722 --- /dev/null +++ b/lib.cjs/options.d.ts @@ -0,0 +1,130 @@ +import type { Readable as NodeReadableStream } from 'node:stream'; +import type { DetailedError } from './DetailedError.js'; +export declare const PROTOCOL_TUS_V1 = "tus-v1"; +export declare const PROTOCOL_IETF_DRAFT_03 = "ietf-draft-03"; +export declare const PROTOCOL_IETF_DRAFT_05 = "ietf-draft-05"; +/** + * ReactNativeFile describes the structure that is returned from the + * Expo image picker (see https://docs.expo.dev/versions/latest/sdk/imagepicker/) + * TODO: Should these properties be fileName and fileSize instead? + * TODO: What about other file pickers without Expo? + * TODO: Should this be renamed to Expo? + * TODO: Only size is relevant for us. Not the rest. + */ +export interface ReactNativeFile { + uri: string; + name?: string; + size?: string; + exif?: Record; +} +/** + * PathReference is a reference to a file on disk. Currently, it's only supported + * in Node.js. It can be supplied as a normal object or as an instance of `fs.ReadStream`, + * which also statisfies this interface. + * + * Optionally, a start and/or end position can be defined to define a range of bytes from + * the file that should be uploaded instead of the entire file. Both start and end are + * inclusive and start counting at 0, similar to the options accepted by `fs.createReadStream`. + */ +export interface PathReference { + path: string | Buffer; + start?: number; + end?: number; +} +export type UploadInput = Blob | ArrayBuffer | SharedArrayBuffer | ArrayBufferView | ReadableStream | NodeReadableStream | PathReference | ReactNativeFile; +export interface UploadOptions { + endpoint?: string; + uploadUrl?: string; + metadata: { + [key: string]: string; + }; + metadataForPartialUploads: UploadOptions['metadata']; + fingerprint: (file: UploadInput, options: UploadOptions) => Promise; + uploadSize?: number; + onProgress?: (bytesSent: number, bytesTotal: number | null) => void; + onChunkComplete?: (chunkSize: number, bytesAccepted: number, bytesTotal: number | null) => void; + onSuccess?: (payload: OnSuccessPayload) => void; + onError?: (error: Error | DetailedError) => void; + onShouldRetry?: (error: DetailedError, retryAttempt: number, options: UploadOptions) => boolean; + onUploadUrlAvailable?: () => void | Promise; + overridePatchMethod: boolean; + headers: { + [key: string]: string; + }; + addRequestId: boolean; + onBeforeRequest?: (req: HttpRequest) => void | Promise; + onAfterResponse?: (req: HttpRequest, res: HttpResponse) => void | Promise; + chunkSize: number; + retryDelays: number[]; + parallelUploads: number; + parallelUploadBoundaries?: { + start: number; + end: number; + }[]; + storeFingerprintForResuming: boolean; + removeFingerprintOnSuccess: boolean; + uploadLengthDeferred: boolean; + uploadDataDuringCreation: boolean; + urlStorage: UrlStorage; + fileReader: FileReader; + httpStack: HttpStack; + protocol: typeof PROTOCOL_TUS_V1 | typeof PROTOCOL_IETF_DRAFT_03 | typeof PROTOCOL_IETF_DRAFT_05; +} +export interface OnSuccessPayload { + lastResponse: HttpResponse; +} +export interface UrlStorage { + findAllUploads(): Promise; + findUploadsByFingerprint(fingerprint: string): Promise; + removeUpload(urlStorageKey: string): Promise; + addUpload(fingerprint: string, upload: PreviousUpload): Promise; +} +export interface PreviousUpload { + size: number | null; + metadata: { + [key: string]: string; + }; + creationTime: string; + uploadUrl?: string; + parallelUploadUrls?: string[]; + urlStorageKey: string; +} +export interface FileReader { + openFile(input: UploadInput, chunkSize: number): Promise; +} +export interface FileSource { + size: number | null; + slice(start: number, end: number): Promise; + close(): void; +} +export type SliceType = Blob | ArrayBufferView | NodeReadableStream; +export type SliceResult = { + done: true; + value: null; + size: null; +} | { + done: boolean; + value: NonNullable; + size: number; +}; +export interface HttpStack { + createRequest(method: string, url: string): HttpRequest; + getName(): string; +} +export type HttpProgressHandler = (bytesSent: number) => void; +export interface HttpRequest { + getMethod(): string; + getURL(): string; + setHeader(header: string, value: string): void; + getHeader(header: string): string | undefined; + setProgressHandler(handler: HttpProgressHandler): void; + send(body?: SliceType): Promise; + abort(): Promise; + getUnderlyingObject(): unknown; +} +export interface HttpResponse { + getStatus(): number; + getHeader(header: string): string | undefined; + getBody(): string; + getUnderlyingObject(): unknown; +} diff --git a/lib.cjs/options.js b/lib.cjs/options.js new file mode 100644 index 00000000..ed7d419b --- /dev/null +++ b/lib.cjs/options.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PROTOCOL_IETF_DRAFT_05 = exports.PROTOCOL_IETF_DRAFT_03 = exports.PROTOCOL_TUS_V1 = void 0; +exports.PROTOCOL_TUS_V1 = 'tus-v1'; +exports.PROTOCOL_IETF_DRAFT_03 = 'ietf-draft-03'; +exports.PROTOCOL_IETF_DRAFT_05 = 'ietf-draft-05'; +//# sourceMappingURL=options.js.map \ No newline at end of file diff --git a/lib.cjs/options.js.map b/lib.cjs/options.js.map new file mode 100644 index 00000000..5d359594 --- /dev/null +++ b/lib.cjs/options.js.map @@ -0,0 +1 @@ +{"version":3,"file":"options.js","sourceRoot":"","sources":["../lib/options.ts"],"names":[],"mappings":";;;AAGa,QAAA,eAAe,GAAG,QAAQ,CAAA;AAC1B,QAAA,sBAAsB,GAAG,eAAe,CAAA;AACxC,QAAA,sBAAsB,GAAG,eAAe,CAAA"} \ No newline at end of file diff --git a/lib.cjs/package.json b/lib.cjs/package.json new file mode 100644 index 00000000..729ac4d9 --- /dev/null +++ b/lib.cjs/package.json @@ -0,0 +1 @@ +{"type":"commonjs"} diff --git a/lib.cjs/reactnative/isReactNative.d.ts b/lib.cjs/reactnative/isReactNative.d.ts new file mode 100644 index 00000000..4f0ff67d --- /dev/null +++ b/lib.cjs/reactnative/isReactNative.d.ts @@ -0,0 +1,3 @@ +import type { ReactNativeFile } from '../options.js'; +export declare function isReactNativePlatform(): boolean; +export declare function isReactNativeFile(input: unknown): input is ReactNativeFile; diff --git a/lib.cjs/reactnative/isReactNative.js b/lib.cjs/reactnative/isReactNative.js new file mode 100644 index 00000000..4cd78d63 --- /dev/null +++ b/lib.cjs/reactnative/isReactNative.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isReactNativePlatform = isReactNativePlatform; +exports.isReactNativeFile = isReactNativeFile; +function isReactNativePlatform() { + return (typeof navigator !== 'undefined' && + typeof navigator.product === 'string' && + navigator.product.toLowerCase() === 'reactnative'); +} +function isReactNativeFile(input) { + return (input != null && typeof input === 'object' && 'uri' in input && typeof input.uri === 'string'); +} +//# sourceMappingURL=isReactNative.js.map \ No newline at end of file diff --git a/lib.cjs/reactnative/isReactNative.js.map b/lib.cjs/reactnative/isReactNative.js.map new file mode 100644 index 00000000..fc26d029 --- /dev/null +++ b/lib.cjs/reactnative/isReactNative.js.map @@ -0,0 +1 @@ +{"version":3,"file":"isReactNative.js","sourceRoot":"","sources":["../../lib/reactnative/isReactNative.ts"],"names":[],"mappings":";;AAEA,sDAMC;AAED,8CAIC;AAZD,SAAgB,qBAAqB;IACnC,OAAO,CACL,OAAO,SAAS,KAAK,WAAW;QAChC,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;QACrC,SAAS,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,aAAa,CAClD,CAAA;AACH,CAAC;AAED,SAAgB,iBAAiB,CAAC,KAAc;IAC9C,OAAO,CACL,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAC9F,CAAA;AACH,CAAC"} \ No newline at end of file diff --git a/lib.cjs/reactnative/uriToBlob.d.ts b/lib.cjs/reactnative/uriToBlob.d.ts new file mode 100644 index 00000000..9f35ddf6 --- /dev/null +++ b/lib.cjs/reactnative/uriToBlob.d.ts @@ -0,0 +1,6 @@ +/** + * 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 declare function uriToBlob(uri: string): Promise; diff --git a/lib.cjs/reactnative/uriToBlob.js b/lib.cjs/reactnative/uriToBlob.js new file mode 100644 index 00000000..a95f8c0e --- /dev/null +++ b/lib.cjs/reactnative/uriToBlob.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.uriToBlob = uriToBlob; +/** + * 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. + */ +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(); + }); +} +//# sourceMappingURL=uriToBlob.js.map \ No newline at end of file diff --git a/lib.cjs/reactnative/uriToBlob.js.map b/lib.cjs/reactnative/uriToBlob.js.map new file mode 100644 index 00000000..0a1e880b --- /dev/null +++ b/lib.cjs/reactnative/uriToBlob.js.map @@ -0,0 +1 @@ +{"version":3,"file":"uriToBlob.js","sourceRoot":"","sources":["../../lib/reactnative/uriToBlob.ts"],"names":[],"mappings":";;AAKA,8BAcC;AAnBD;;;;GAIG;AACH,SAAgB,SAAS,CAAC,GAAW;IACnC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,cAAc,EAAE,CAAA;QAChC,GAAG,CAAC,YAAY,GAAG,MAAM,CAAA;QACzB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE;YAChB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAA;YACzB,OAAO,CAAC,IAAI,CAAC,CAAA;QACf,CAAC,CAAA;QACD,GAAG,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,EAAE;YACpB,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC,CAAA;QACD,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QACpB,GAAG,CAAC,IAAI,EAAE,CAAA;IACZ,CAAC,CAAC,CAAA;AACJ,CAAC"} \ No newline at end of file diff --git a/lib.cjs/sources/ArrayBufferViewFileSource.d.ts b/lib.cjs/sources/ArrayBufferViewFileSource.d.ts new file mode 100644 index 00000000..e540a35c --- /dev/null +++ b/lib.cjs/sources/ArrayBufferViewFileSource.d.ts @@ -0,0 +1,15 @@ +import type { FileSource, SliceResult } from '../options.js'; +/** + * ArrayBufferViewFileSource implements FileSource for ArrayBufferView instances + * (e.g. TypedArry or DataView). + * + * Note that the underlying ArrayBuffer should not change once passed to tus-js-client + * or it will lead to weird behavior. + */ +export declare class ArrayBufferViewFileSource implements FileSource { + private _view; + size: number; + constructor(view: ArrayBufferView); + slice(start: number, end: number): Promise; + close(): void; +} diff --git a/lib.cjs/sources/ArrayBufferViewFileSource.js b/lib.cjs/sources/ArrayBufferViewFileSource.js new file mode 100644 index 00000000..53172083 --- /dev/null +++ b/lib.cjs/sources/ArrayBufferViewFileSource.js @@ -0,0 +1,32 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ArrayBufferViewFileSource = void 0; +/** + * ArrayBufferViewFileSource implements FileSource for ArrayBufferView instances + * (e.g. TypedArry or DataView). + * + * Note that the underlying ArrayBuffer should not change once passed to tus-js-client + * or it will lead to weird behavior. + */ +class ArrayBufferViewFileSource { + constructor(view) { + this._view = view; + this.size = view.byteLength; + } + slice(start, end) { + const buffer = this._view.buffer; + const startInBuffer = this._view.byteOffset + start; + end = Math.min(end, this.size); // ensure end is finite and not greater than size + const byteLength = end - start; + // Use DataView instead of ArrayBuffer.slice to avoid copying the buffer. + const value = new DataView(buffer, startInBuffer, byteLength); + const size = value.byteLength; + const done = end >= this.size; + return Promise.resolve({ value, size, done }); + } + close() { + // Nothing to do here since we don't need to release any resources. + } +} +exports.ArrayBufferViewFileSource = ArrayBufferViewFileSource; +//# sourceMappingURL=ArrayBufferViewFileSource.js.map \ No newline at end of file diff --git a/lib.cjs/sources/ArrayBufferViewFileSource.js.map b/lib.cjs/sources/ArrayBufferViewFileSource.js.map new file mode 100644 index 00000000..01d00c08 --- /dev/null +++ b/lib.cjs/sources/ArrayBufferViewFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ArrayBufferViewFileSource.js","sourceRoot":"","sources":["../../lib/sources/ArrayBufferViewFileSource.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACH,MAAa,yBAAyB;IAKpC,YAAY,IAAqB;QAC/B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,UAAU,CAAA;IAC7B,CAAC;IAED,KAAK,CAAC,KAAa,EAAE,GAAW;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;QAChC,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAA;QACnD,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA,CAAC,iDAAiD;QAChF,MAAM,UAAU,GAAG,GAAG,GAAG,KAAK,CAAA;QAE9B,yEAAyE;QACzE,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,CAAC,CAAA;QAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAA;QAC7B,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAA;QAE7B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/C,CAAC;IAED,KAAK;QACH,mEAAmE;IACrE,CAAC;CACF;AA3BD,8DA2BC"} \ No newline at end of file diff --git a/lib.cjs/sources/BlobFileSource.d.ts b/lib.cjs/sources/BlobFileSource.d.ts new file mode 100644 index 00000000..fb48b7da --- /dev/null +++ b/lib.cjs/sources/BlobFileSource.d.ts @@ -0,0 +1,11 @@ +import type { FileSource, SliceResult } from '../options.js'; +/** + * BlobFileSource implements FileSource for Blobs (and therefore also for File instances). + */ +export declare class BlobFileSource implements FileSource { + private _file; + size: number; + constructor(file: Blob); + slice(start: number, end: number): Promise; + close(): void; +} diff --git a/lib.cjs/sources/BlobFileSource.js b/lib.cjs/sources/BlobFileSource.js new file mode 100644 index 00000000..66faa8cd --- /dev/null +++ b/lib.cjs/sources/BlobFileSource.js @@ -0,0 +1,36 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BlobFileSource = void 0; +const isCordova_js_1 = require("../cordova/isCordova.js"); +const readAsByteArray_js_1 = require("../cordova/readAsByteArray.js"); +/** + * BlobFileSource implements FileSource for Blobs (and therefore also for File instances). + */ +class BlobFileSource { + constructor(file) { + this._file = file; + this.size = file.size; + } + async slice(start, end) { + // TODO: This looks fishy. We should test how this actually works in Cordova + // and consider moving this into the lib/cordova/ directory. + // 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 ((0, isCordova_js_1.isCordova)()) { + const value = await (0, readAsByteArray_js_1.readAsByteArray)(this._file.slice(start, end)); + const size = value.length; + const done = end >= this.size; + return { value, size, done }; + } + const value = this._file.slice(start, end); + const size = value.size; + const done = end >= this.size; + return { value, size, done }; + } + close() { + // Nothing to do here since we don't need to release any resources. + } +} +exports.BlobFileSource = BlobFileSource; +//# sourceMappingURL=BlobFileSource.js.map \ No newline at end of file diff --git a/lib.cjs/sources/BlobFileSource.js.map b/lib.cjs/sources/BlobFileSource.js.map new file mode 100644 index 00000000..99026a93 --- /dev/null +++ b/lib.cjs/sources/BlobFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"BlobFileSource.js","sourceRoot":"","sources":["../../lib/sources/BlobFileSource.ts"],"names":[],"mappings":";;;AAAA,0DAAmD;AACnD,sEAA+D;AAG/D;;GAEG;AACH,MAAa,cAAc;IAKzB,YAAY,IAAU;QACpB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;IACvB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,GAAW;QACpC,4EAA4E;QAC5E,4DAA4D;QAC5D,gEAAgE;QAChE,4BAA4B;QAC5B,8FAA8F;QAC9F,IAAI,IAAA,wBAAS,GAAE,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG,MAAM,IAAA,oCAAe,EAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAA;YACjE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAA;YACzB,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAA;YAE7B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;QAC9B,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;QACvB,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAA;QAE7B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC9B,CAAC;IAED,KAAK;QACH,mEAAmE;IACrE,CAAC;CACF;AAlCD,wCAkCC"} \ No newline at end of file diff --git a/lib.cjs/sources/WebStreamFileSource.d.ts b/lib.cjs/sources/WebStreamFileSource.d.ts new file mode 100644 index 00000000..80bb2fc5 --- /dev/null +++ b/lib.cjs/sources/WebStreamFileSource.d.ts @@ -0,0 +1,16 @@ +import type { FileSource, SliceResult } from '../options.js'; +/** + * WebStreamFileSource implements FileSource for Web Streams. + */ +export declare class WebStreamFileSource implements FileSource { + private _reader; + private _buffer; + private _bufferOffset; + private _done; + size: null; + constructor(stream: ReadableStream); + slice(start: number, end: number): Promise; + private _readUntilEnoughDataOrDone; + private _getDataFromBuffer; + close(): void; +} diff --git a/lib.cjs/sources/WebStreamFileSource.js b/lib.cjs/sources/WebStreamFileSource.js new file mode 100644 index 00000000..c56a5952 --- /dev/null +++ b/lib.cjs/sources/WebStreamFileSource.js @@ -0,0 +1,111 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WebStreamFileSource = void 0; +function len(blobOrArray) { + if (blobOrArray === undefined) + return 0; + if (blobOrArray instanceof Blob) + 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 instanceof Blob && b instanceof Blob) { + return new Blob([a, b], { type: a.type }); + } + if (a instanceof Uint8Array && b instanceof Uint8Array) { + const c = new Uint8Array(a.length + b.length); + c.set(a); + c.set(b, a.length); + return c; + } + throw new Error('Unknown data type'); +} +/** + * WebStreamFileSource implements FileSource for Web Streams. + */ +// TODO: Can we share code with NodeStreamFileSource? +class WebStreamFileSource { + constructor(stream) { + // _bufferOffset defines at which position the content of _buffer (if it is set) + // is located in the view of the entire stream. It does not mean at which offset + // the content in _buffer begins. + this._bufferOffset = 0; + this._done = false; + // Setting the size to null indicates that we have no calculation available + // for how much data this stream will emit requiring the user to specify + // it manually (see the `uploadSize` option). + this.size = null; + if (stream.locked) { + throw new Error('Readable stream is already locked to reader. tus-js-client cannot obtain a new reader.'); + } + this._reader = stream.getReader(); + } + async slice(start, end) { + if (start < this._bufferOffset) { + throw new Error("Requested data is before the reader's current offset"); + } + return await this._readUntilEnoughDataOrDone(start, end); + } + async _readUntilEnoughDataOrDone(start, end) { + const hasEnoughData = end <= this._bufferOffset + len(this._buffer); + if (this._done || hasEnoughData) { + const value = this._getDataFromBuffer(start, end); + if (value === null) { + return { value: null, size: null, done: true }; + } + const size = value instanceof Blob ? value.size : value.length; + const done = this._done; + return { value, size, done }; + } + const { value, done } = await this._reader.read(); + if (done) { + this._done = true; + } + else { + const chunkSize = len(value); + // If all of the chunk occurs before 'start' then drop it and clear the buffer. + // This greatly improves performance when reading from a stream we haven't started processing yet and 'start' is near the end of the file. + // Rather than buffering all of the unused data in memory just to only read a chunk near the end, rather immidiately drop data which will never be read. + if (this._bufferOffset + len(this._buffer) + chunkSize < start) { + this._buffer = undefined; + this._bufferOffset += chunkSize; + } + else if (this._buffer === undefined) { + this._buffer = value; + } + else { + this._buffer = concat(this._buffer, value); + } + } + return await this._readUntilEnoughDataOrDone(start, end); + } + _getDataFromBuffer(start, end) { + if (this._buffer === undefined) { + throw new Error('cannot _getDataFromBuffer because _buffer is unset'); + } + // 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() { + this._reader.cancel(); + } +} +exports.WebStreamFileSource = WebStreamFileSource; +//# sourceMappingURL=WebStreamFileSource.js.map \ No newline at end of file diff --git a/lib.cjs/sources/WebStreamFileSource.js.map b/lib.cjs/sources/WebStreamFileSource.js.map new file mode 100644 index 00000000..db45dfd7 --- /dev/null +++ b/lib.cjs/sources/WebStreamFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WebStreamFileSource.js","sourceRoot":"","sources":["../../lib/sources/WebStreamFileSource.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.cjs/upload.d.ts b/lib.cjs/upload.d.ts new file mode 100644 index 00000000..f19b1399 --- /dev/null +++ b/lib.cjs/upload.d.ts @@ -0,0 +1,189 @@ +import { DetailedError } from './DetailedError.js'; +import { type HttpRequest, type HttpResponse, type PreviousUpload, type SliceType, type UploadInput, type UploadOptions } from './options.js'; +export declare const defaultOptions: { + endpoint: undefined; + uploadUrl: undefined; + metadata: {}; + metadataForPartialUploads: {}; + fingerprint: undefined; + uploadSize: undefined; + onProgress: undefined; + onChunkComplete: undefined; + onSuccess: undefined; + onError: undefined; + onUploadUrlAvailable: undefined; + overridePatchMethod: boolean; + headers: {}; + addRequestId: boolean; + onBeforeRequest: undefined; + onAfterResponse: undefined; + onShouldRetry: typeof defaultOnShouldRetry; + chunkSize: number; + retryDelays: number[]; + parallelUploads: number; + parallelUploadBoundaries: undefined; + storeFingerprintForResuming: boolean; + removeFingerprintOnSuccess: boolean; + uploadLengthDeferred: boolean; + uploadDataDuringCreation: boolean; + urlStorage: undefined; + fileReader: undefined; + httpStack: undefined; + protocol: UploadOptions["protocol"]; +}; +export declare class BaseUpload { + options: UploadOptions; + file: UploadInput; + url: string | null; + private _req?; + private _fingerprint; + private _urlStorageKey?; + private _offset; + private _aborted; + private _size; + private _source?; + private _retryAttempt; + private _retryTimeout?; + private _offsetBeforeRetry; + private _parallelUploads?; + private _parallelUploadUrls?; + private _uploadLengthDeferred; + constructor(file: UploadInput, options: UploadOptions); + findPreviousUploads(): Promise; + resumeFromPreviousUpload(previousUpload: PreviousUpload): void; + start(): void; + private _prepareAndStartUpload; + /** + * Initiate the uploading procedure for a parallelized upload, where one file is split into + * multiple request which are run in parallel. + * + * @api private + */ + private _startParallelUpload; + /** + * Initiate the uploading procedure for a non-parallel upload. Here the entire file is + * uploaded in a sequential matter. + * + * @api private + */ + private _startSingleUpload; + /** + * Abort any running request and stop the current upload. After abort is called, no event + * handler will be invoked anymore. You can use the `start` method to resume the upload + * again. + * If `shouldTerminate` is true, the `terminate` function will be called to remove the + * current upload from the server. + * + * @param {boolean} shouldTerminate True if the upload should be deleted from the server. + * @return {Promise} The Promise will be resolved/rejected when the requests finish. + */ + abort(shouldTerminate?: boolean): Promise; + private _emitError; + private _retryOrEmitError; + /** + * Publishes notification if the upload has been successfully completed. + * + * @param {object} lastResponse Last HTTP response. + * @api private + */ + private _emitSuccess; + /** + * Publishes notification when data has been sent to the server. This + * data may not have been accepted by the server yet. + * + * @param {number} bytesSent Number of bytes sent to the server. + * @param {number|null} bytesTotal Total number of bytes to be sent to the server. + * @api private + */ + private _emitProgress; + /** + * Publishes notification when a chunk of data has been sent to the server + * and accepted by the server. + * @param {number} chunkSize Size of the chunk that was accepted by the server. + * @param {number} bytesAccepted Total number of bytes that have been + * accepted by the server. + * @param {number|null} bytesTotal Total number of bytes to be sent to the server. + * @api private + */ + private _emitChunkComplete; + /** + * Create a new upload using the creation extension by sending a POST + * request to the endpoint. After successful creation the file will be + * uploaded + * + * @api private + */ + private _createUpload; + /** + * Try to resume an existing upload. First a HEAD request will be sent + * to retrieve the offset. If the request fails a new upload will be + * created. In the case of a successful response the file will be uploaded. + * + * @api private + */ + private _resumeUpload; + /** + * Start uploading the file using PATCH requests. The file will be divided + * into chunks as specified in the chunkSize option. During the upload + * the onProgress event handler may be invoked multiple times. + * + * @api private + */ + private _performUpload; + /** + * _addChunktoRequest reads a chunk from the source and sends it using the + * supplied request object. It will not handle the response. + * + * @api private + */ + private _addChunkToRequest; + /** + * _handleUploadResponse is used by requests that haven been sent using _addChunkToRequest + * and already have received a response. + * + * @api private + */ + private _handleUploadResponse; + /** + * Create a new HTTP request object with the given method and URL. + * + * @api private + */ + private _openRequest; + /** + * Remove the entry in the URL storage, if it has been saved before. + * + * @api private + */ + private _removeFromUrlStorage; + /** + * Add the upload URL to the URL storage, if possible. + * + * @api private + */ + private _saveUploadInUrlStorage; + /** + * Send a request with the provided body. + * + * @api private + */ + _sendRequest(req: HttpRequest, body?: SliceType): Promise; +} +/** + * determines if the request should be retried. Will only retry if not a status 4xx except a 409 or 423 + * @param {DetailedError} err + * @returns {boolean} + */ +declare function defaultOnShouldRetry(err: DetailedError): boolean; +/** + * Use the Termination extension to delete an upload from the server by sending a DELETE + * request to the specified upload URL. This is only possible if the server supports the + * Termination extension. If the `options.retryDelays` property is set, the method will + * also retry if an error ocurrs. + * + * @param {String} url The upload's URL which will be terminated. + * @param {object} options Optional options for influencing HTTP requests. + * @return {Promise} The Promise will be resolved/rejected when the requests finish. + */ +export declare function terminate(url: string, options: UploadOptions): Promise; +export {}; diff --git a/lib.cjs/upload.js b/lib.cjs/upload.js new file mode 100644 index 00000000..bddd5941 --- /dev/null +++ b/lib.cjs/upload.js @@ -0,0 +1,987 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BaseUpload = exports.defaultOptions = void 0; +exports.terminate = terminate; +const js_base64_1 = require("js-base64"); +// TODO: Package url-parse is CommonJS. Can we replace this with a ESM package that +// provides WHATWG URL? Then we can get rid of @rollup/plugin-commonjs. +const url_parse_1 = __importDefault(require("url-parse")); +const DetailedError_js_1 = require("./DetailedError.js"); +const logger_js_1 = require("./logger.js"); +const options_js_1 = require("./options.js"); +const uuid_js_1 = require("./uuid.js"); +exports.defaultOptions = { + endpoint: undefined, + uploadUrl: undefined, + metadata: {}, + metadataForPartialUploads: {}, + fingerprint: undefined, + uploadSize: undefined, + onProgress: undefined, + onChunkComplete: undefined, + onSuccess: undefined, + onError: undefined, + onUploadUrlAvailable: undefined, + overridePatchMethod: false, + headers: {}, + addRequestId: false, + onBeforeRequest: undefined, + onAfterResponse: undefined, + onShouldRetry: defaultOnShouldRetry, + chunkSize: Number.POSITIVE_INFINITY, + retryDelays: [0, 1000, 3000, 5000], + parallelUploads: 1, + parallelUploadBoundaries: undefined, + storeFingerprintForResuming: true, + removeFingerprintOnSuccess: false, + uploadLengthDeferred: false, + uploadDataDuringCreation: false, + urlStorage: undefined, + fileReader: undefined, + httpStack: undefined, + protocol: options_js_1.PROTOCOL_TUS_V1, +}; +class BaseUpload { + constructor(file, options) { + // The URL against which the file will be uploaded + this.url = null; + // The fingerpinrt for the current file (set after start()) + this._fingerprint = null; + // The offset used in the current PATCH request + this._offset = 0; + // True if the current PATCH request has been aborted + this._aborted = false; + // The file's size in bytes + this._size = null; + // The current count of attempts which have been made. Zero indicates none. + this._retryAttempt = 0; + // The offset of the remote upload before the latest attempt was started. + this._offsetBeforeRetry = 0; + // Warn about removed options from previous versions + if ('resume' in options) { + console.log('tus: The `resume` option has been removed in tus-js-client v2. Please use the URL storage API instead.'); + } + // The default options will already be added from the wrapper classes. + this.options = options; + // Cast chunkSize to integer + // TODO: Remove this cast + this.options.chunkSize = Number(this.options.chunkSize); + this._uploadLengthDeferred = this.options.uploadLengthDeferred; + this.file = file; + } + async findPreviousUploads() { + const fingerprint = await this.options.fingerprint(this.file, this.options); + if (!fingerprint) { + throw new Error('tus: unable to calculate fingerprint for this input file'); + } + return await this.options.urlStorage.findUploadsByFingerprint(fingerprint); + } + resumeFromPreviousUpload(previousUpload) { + this.url = previousUpload.uploadUrl || null; + this._parallelUploadUrls = previousUpload.parallelUploadUrls; + this._urlStorageKey = previousUpload.urlStorageKey; + } + start() { + if (!this.file) { + this._emitError(new Error('tus: no file or stream to upload provided')); + return; + } + if (![options_js_1.PROTOCOL_TUS_V1, options_js_1.PROTOCOL_IETF_DRAFT_03, options_js_1.PROTOCOL_IETF_DRAFT_05].includes(this.options.protocol)) { + this._emitError(new Error(`tus: unsupported protocol ${this.options.protocol}`)); + return; + } + if (!this.options.endpoint && !this.options.uploadUrl && !this.url) { + this._emitError(new Error('tus: neither an endpoint or an upload URL is provided')); + return; + } + const { retryDelays } = this.options; + if (retryDelays != null && Object.prototype.toString.call(retryDelays) !== '[object Array]') { + this._emitError(new Error('tus: the `retryDelays` option must either be an array or null')); + return; + } + if (this.options.parallelUploads > 1) { + // Test which options are incompatible with parallel uploads. + if (this.options.uploadUrl != null) { + this._emitError(new Error('tus: cannot use the `uploadUrl` option when parallelUploads is enabled')); + return; + } + if (this.options.uploadSize != null) { + this._emitError(new Error('tus: cannot use the `uploadSize` option when parallelUploads is enabled')); + return; + } + if (this._uploadLengthDeferred) { + this._emitError(new Error('tus: cannot use the `uploadLengthDeferred` option when parallelUploads is enabled')); + return; + } + } + if (this.options.parallelUploadBoundaries) { + if (this.options.parallelUploads <= 1) { + this._emitError(new Error('tus: cannot use the `parallelUploadBoundaries` option when `parallelUploads` is disabled')); + return; + } + if (this.options.parallelUploads !== this.options.parallelUploadBoundaries.length) { + this._emitError(new Error('tus: the `parallelUploadBoundaries` must have the same length as the value of `parallelUploads`')); + return; + } + } + // Note: `start` does not return a Promise or await the preparation on purpose. + // Its supposed to return immediately and start the upload in the background. + this._prepareAndStartUpload().catch((err) => { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + // Errors from the actual upload requests will bubble up to here, where + // we then consider retrying them. Other functions should not call _emitError on their own. + this._retryOrEmitError(err); + }); + } + async _prepareAndStartUpload() { + this._fingerprint = await this.options.fingerprint(this.file, this.options); + if (this._fingerprint == null) { + (0, logger_js_1.log)('No fingerprint was calculated meaning that the upload cannot be stored in the URL storage.'); + } + else { + (0, logger_js_1.log)(`Calculated fingerprint: ${this._fingerprint}`); + } + if (this._source == null) { + this._source = await this.options.fileReader.openFile(this.file, this.options.chunkSize); + } + // First, we look at the uploadLengthDeferred option. + // Next, we check if the caller has supplied a manual upload size. + // Finally, we try to use the calculated size from the source object. + if (this._uploadLengthDeferred) { + this._size = null; + } + else if (this.options.uploadSize != null) { + this._size = Number(this.options.uploadSize); + if (Number.isNaN(this._size)) { + throw new Error('tus: cannot convert `uploadSize` option into a number'); + } + } + else { + this._size = this._source.size; + if (this._size == null) { + throw new Error("tus: cannot automatically derive upload's size from input. Specify it manually using the `uploadSize` option or use the `uploadLengthDeferred` option"); + } + } + // If the upload was configured to use multiple requests or if we resume from + // an upload which used multiple requests, we start a parallel upload. + if (this.options.parallelUploads > 1 || this._parallelUploadUrls != null) { + await this._startParallelUpload(); + } + else { + await this._startSingleUpload(); + } + } + /** + * Initiate the uploading procedure for a parallelized upload, where one file is split into + * multiple request which are run in parallel. + * + * @api private + */ + async _startParallelUpload() { + var _a; + const totalSize = this._size; + let totalProgress = 0; + this._parallelUploads = []; + const partCount = this._parallelUploadUrls != null + ? this._parallelUploadUrls.length + : this.options.parallelUploads; + if (this._size == null) { + throw new Error('tus: Expected _size to be set'); + } + // The input file will be split into multiple slices which are uploaded in separate + // requests. Here we get the start and end position for the slices. + const partsBoundaries = (_a = this.options.parallelUploadBoundaries) !== null && _a !== void 0 ? _a : splitSizeIntoParts(this._size, partCount); + // Attach URLs from previous uploads, if available. + const parts = partsBoundaries.map((part, index) => { + var _a; + return ({ + ...part, + uploadUrl: ((_a = this._parallelUploadUrls) === null || _a === void 0 ? void 0 : _a[index]) || null, + }); + }); + // Create an empty list for storing the upload URLs + this._parallelUploadUrls = new Array(parts.length); + // Generate a promise for each slice that will be resolve if the respective + // upload is completed. + const uploads = parts.map(async (part, index) => { + let lastPartProgress = 0; + // @ts-expect-error We know that `_source` is not null here. + const { value } = await this._source.slice(part.start, part.end); + return new Promise((resolve, reject) => { + // Merge with the user supplied options but overwrite some values. + const options = { + ...this.options, + // If available, the partial upload should be resumed from a previous URL. + uploadUrl: part.uploadUrl || null, + // We take manually care of resuming for partial uploads, so they should + // not be stored in the URL storage. + storeFingerprintForResuming: false, + removeFingerprintOnSuccess: false, + // Reset the parallelUploads option to not cause recursion. + parallelUploads: 1, + // Reset this option as we are not doing a parallel upload. + parallelUploadBoundaries: null, + metadata: this.options.metadataForPartialUploads, + // Add the header to indicate the this is a partial upload. + headers: { + ...this.options.headers, + 'Upload-Concat': 'partial', + }, + // Reject or resolve the promise if the upload errors or completes. + onSuccess: resolve, + onError: reject, + // Based in the progress for this partial upload, calculate the progress + // for the entire final upload. + onProgress: (newPartProgress) => { + totalProgress = totalProgress - lastPartProgress + newPartProgress; + lastPartProgress = newPartProgress; + if (totalSize == null) { + throw new Error('tus: Expected totalSize to be set'); + } + this._emitProgress(totalProgress, totalSize); + }, + // Wait until every partial upload has an upload URL, so we can add + // them to the URL storage. + onUploadUrlAvailable: async () => { + // @ts-expect-error We know that _parallelUploadUrls is defined + this._parallelUploadUrls[index] = upload.url; + // Test if all uploads have received an URL + // @ts-expect-error We know that _parallelUploadUrls is defined + if (this._parallelUploadUrls.filter((u) => Boolean(u)).length === parts.length) { + await this._saveUploadInUrlStorage(); + } + }, + }; + if (value == null) { + reject(new Error('tus: no value returned while slicing file for parallel uploads')); + return; + } + // @ts-expect-error `value` is unknown and not an UploadInput + const upload = new BaseUpload(value, options); + upload.start(); + // Store the upload in an array, so we can later abort them if necessary. + // @ts-expect-error We know that _parallelUploadUrls is defined + this._parallelUploads.push(upload); + }); + }); + // Wait until all partial uploads are finished and we can send the POST request for + // creating the final upload. + await Promise.all(uploads); + if (this.options.endpoint == null) { + throw new Error('tus: Expected options.endpoint to be set'); + } + const req = this._openRequest('POST', this.options.endpoint); + req.setHeader('Upload-Concat', `final;${this._parallelUploadUrls.join(' ')}`); + // Add metadata if values have been added + const metadata = encodeMetadata(this.options.metadata); + if (metadata !== '') { + req.setHeader('Upload-Metadata', metadata); + } + let res; + try { + res = await this._sendRequest(req); + } + catch (err) { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + throw new DetailedError_js_1.DetailedError('tus: failed to concatenate parallel uploads', err, req, undefined); + } + if (!inStatusCategory(res.getStatus(), 200)) { + throw new DetailedError_js_1.DetailedError('tus: unexpected response while creating upload', undefined, req, res); + } + const location = res.getHeader('Location'); + if (location == null) { + throw new DetailedError_js_1.DetailedError('tus: invalid or missing Location header', undefined, req, res); + } + if (this.options.endpoint == null) { + throw new Error('tus: Expeced endpoint to be defined.'); + } + this.url = resolveUrl(this.options.endpoint, location); + (0, logger_js_1.log)(`Created upload at ${this.url}`); + await this._emitSuccess(res); + } + /** + * Initiate the uploading procedure for a non-parallel upload. Here the entire file is + * uploaded in a sequential matter. + * + * @api private + */ + async _startSingleUpload() { + // Reset the aborted flag when the upload is started or else the + // _performUpload will stop before sending a request if the upload has been + // aborted previously. + this._aborted = false; + // The upload had been started previously and we should reuse this URL. + if (this.url != null) { + (0, logger_js_1.log)(`Resuming upload from previous URL: ${this.url}`); + return await this._resumeUpload(); + } + // A URL has manually been specified, so we try to resume + if (this.options.uploadUrl != null) { + (0, logger_js_1.log)(`Resuming upload from provided URL: ${this.options.uploadUrl}`); + this.url = this.options.uploadUrl; + return await this._resumeUpload(); + } + // An upload has not started for the file yet, so we start a new one + (0, logger_js_1.log)('Creating a new upload'); + return await this._createUpload(); + } + /** + * Abort any running request and stop the current upload. After abort is called, no event + * handler will be invoked anymore. You can use the `start` method to resume the upload + * again. + * If `shouldTerminate` is true, the `terminate` function will be called to remove the + * current upload from the server. + * + * @param {boolean} shouldTerminate True if the upload should be deleted from the server. + * @return {Promise} The Promise will be resolved/rejected when the requests finish. + */ + async abort(shouldTerminate = false) { + // Set the aborted flag before any `await`s, so no new requests are started. + this._aborted = true; + // Stop any parallel partial uploads, that have been started in _startParallelUploads. + if (this._parallelUploads != null) { + for (const upload of this._parallelUploads) { + await upload.abort(shouldTerminate); + } + } + // Stop any current running request. + if (this._req != null) { + await this._req.abort(); + // Note: We do not close the file source here, so the user can resume in the future. + } + // Stop any timeout used for initiating a retry. + if (this._retryTimeout != null) { + clearTimeout(this._retryTimeout); + this._retryTimeout = undefined; + } + if (shouldTerminate && this.url != null) { + await terminate(this.url, this.options); + // Remove entry from the URL storage since the upload URL is no longer valid. + await this._removeFromUrlStorage(); + } + } + _emitError(err) { + // Do not emit errors, e.g. from aborted HTTP requests, if the upload has been stopped. + if (this._aborted) + return; + if (typeof this.options.onError === 'function') { + this.options.onError(err); + } + else { + throw err; + } + } + _retryOrEmitError(err) { + // Do not retry if explicitly aborted + if (this._aborted) + return; + // Check if we should retry, when enabled, before sending the error to the user. + if (this.options.retryDelays != null) { + // We will reset the attempt counter if + // - we were already able to connect to the server (offset != null) and + // - we were able to upload a small chunk of data to the server + const shouldResetDelays = this._offset != null && this._offset > this._offsetBeforeRetry; + if (shouldResetDelays) { + this._retryAttempt = 0; + } + if (shouldRetry(err, this._retryAttempt, this.options)) { + const delay = this.options.retryDelays[this._retryAttempt++]; + this._offsetBeforeRetry = this._offset; + this._retryTimeout = setTimeout(() => { + this.start(); + }, delay); + return; + } + } + // If we are not retrying, emit the error to the user. + this._emitError(err); + } + /** + * Publishes notification if the upload has been successfully completed. + * + * @param {object} lastResponse Last HTTP response. + * @api private + */ + async _emitSuccess(lastResponse) { + if (this.options.removeFingerprintOnSuccess) { + // Remove stored fingerprint and corresponding endpoint. This causes + // new uploads of the same file to be treated as a different file. + await this._removeFromUrlStorage(); + } + if (typeof this.options.onSuccess === 'function') { + this.options.onSuccess({ lastResponse }); + } + } + /** + * Publishes notification when data has been sent to the server. This + * data may not have been accepted by the server yet. + * + * @param {number} bytesSent Number of bytes sent to the server. + * @param {number|null} bytesTotal Total number of bytes to be sent to the server. + * @api private + */ + _emitProgress(bytesSent, bytesTotal) { + if (typeof this.options.onProgress === 'function') { + this.options.onProgress(bytesSent, bytesTotal); + } + } + /** + * Publishes notification when a chunk of data has been sent to the server + * and accepted by the server. + * @param {number} chunkSize Size of the chunk that was accepted by the server. + * @param {number} bytesAccepted Total number of bytes that have been + * accepted by the server. + * @param {number|null} bytesTotal Total number of bytes to be sent to the server. + * @api private + */ + _emitChunkComplete(chunkSize, bytesAccepted, bytesTotal) { + if (typeof this.options.onChunkComplete === 'function') { + this.options.onChunkComplete(chunkSize, bytesAccepted, bytesTotal); + } + } + /** + * Create a new upload using the creation extension by sending a POST + * request to the endpoint. After successful creation the file will be + * uploaded + * + * @api private + */ + async _createUpload() { + if (!this.options.endpoint) { + throw new Error('tus: unable to create upload because no endpoint is provided'); + } + const req = this._openRequest('POST', this.options.endpoint); + if (this._uploadLengthDeferred) { + req.setHeader('Upload-Defer-Length', '1'); + } + else { + if (this._size == null) { + throw new Error('tus: expected _size to be set'); + } + req.setHeader('Upload-Length', `${this._size}`); + } + // Add metadata if values have been added + const metadata = encodeMetadata(this.options.metadata); + if (metadata !== '') { + req.setHeader('Upload-Metadata', metadata); + } + let res; + try { + if (this.options.uploadDataDuringCreation && !this._uploadLengthDeferred) { + this._offset = 0; + res = await this._addChunkToRequest(req); + } + else { + if (this.options.protocol === options_js_1.PROTOCOL_IETF_DRAFT_03 || + this.options.protocol === options_js_1.PROTOCOL_IETF_DRAFT_05) { + req.setHeader('Upload-Complete', '?0'); + } + res = await this._sendRequest(req); + } + } + catch (err) { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + throw new DetailedError_js_1.DetailedError('tus: failed to create upload', err, req, undefined); + } + if (!inStatusCategory(res.getStatus(), 200)) { + throw new DetailedError_js_1.DetailedError('tus: unexpected response while creating upload', undefined, req, res); + } + const location = res.getHeader('Location'); + if (location == null) { + throw new DetailedError_js_1.DetailedError('tus: invalid or missing Location header', undefined, req, res); + } + if (this.options.endpoint == null) { + throw new Error('tus: Expected options.endpoint to be set'); + } + this.url = resolveUrl(this.options.endpoint, location); + (0, logger_js_1.log)(`Created upload at ${this.url}`); + if (typeof this.options.onUploadUrlAvailable === 'function') { + await this.options.onUploadUrlAvailable(); + } + if (this._size === 0) { + // Nothing to upload and file was successfully created + await this._emitSuccess(res); + if (this._source) + this._source.close(); + return; + } + await this._saveUploadInUrlStorage(); + if (this.options.uploadDataDuringCreation) { + await this._handleUploadResponse(req, res); + } + else { + this._offset = 0; + await this._performUpload(); + } + } + /** + * Try to resume an existing upload. First a HEAD request will be sent + * to retrieve the offset. If the request fails a new upload will be + * created. In the case of a successful response the file will be uploaded. + * + * @api private + */ + async _resumeUpload() { + if (this.url == null) { + throw new Error('tus: Expected url to be set'); + } + const req = this._openRequest('HEAD', this.url); + let res; + try { + res = await this._sendRequest(req); + } + catch (err) { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + throw new DetailedError_js_1.DetailedError('tus: failed to resume upload', err, req, undefined); + } + const status = res.getStatus(); + if (!inStatusCategory(status, 200)) { + // If the upload is locked (indicated by the 423 Locked status code), we + // emit an error instead of directly starting a new upload. This way the + // retry logic can catch the error and will retry the upload. An upload + // is usually locked for a short period of time and will be available + // afterwards. + if (status === 423) { + throw new DetailedError_js_1.DetailedError('tus: upload is currently locked; retry later', undefined, req, res); + } + if (inStatusCategory(status, 400)) { + // Remove stored fingerprint and corresponding endpoint, + // on client errors since the file can not be found + await this._removeFromUrlStorage(); + } + if (!this.options.endpoint) { + // Don't attempt to create a new upload if no endpoint is provided. + throw new DetailedError_js_1.DetailedError('tus: unable to resume upload (new upload cannot be created without an endpoint)', undefined, req, res); + } + // Try to create a new upload + this.url = null; + await this._createUpload(); + } + const offsetStr = res.getHeader('Upload-Offset'); + if (offsetStr === undefined) { + throw new DetailedError_js_1.DetailedError('tus: missing Upload-Offset header', undefined, req, res); + } + const offset = Number.parseInt(offsetStr, 10); + if (Number.isNaN(offset)) { + throw new DetailedError_js_1.DetailedError('tus: invalid Upload-Offset header', undefined, req, res); + } + const deferLength = res.getHeader('Upload-Defer-Length'); + this._uploadLengthDeferred = deferLength === '1'; + // @ts-expect-error parseInt also handles undefined as we want it to + const length = Number.parseInt(res.getHeader('Upload-Length'), 10); + if (Number.isNaN(length) && + !this._uploadLengthDeferred && + this.options.protocol === options_js_1.PROTOCOL_TUS_V1) { + throw new DetailedError_js_1.DetailedError('tus: invalid or missing length value', undefined, req, res); + } + if (typeof this.options.onUploadUrlAvailable === 'function') { + await this.options.onUploadUrlAvailable(); + } + await this._saveUploadInUrlStorage(); + // Upload has already been completed and we do not need to send additional + // data to the server + if (offset === length) { + this._emitProgress(length, length); + await this._emitSuccess(res); + return; + } + this._offset = offset; + await this._performUpload(); + } + /** + * Start uploading the file using PATCH requests. The file will be divided + * into chunks as specified in the chunkSize option. During the upload + * the onProgress event handler may be invoked multiple times. + * + * @api private + */ + async _performUpload() { + // If the upload has been aborted, we will not send the next PATCH request. + // This is important if the abort method was called during a callback, such + // as onChunkComplete or onProgress. + if (this._aborted) { + return; + } + let req; + if (this.url == null) { + throw new Error('tus: Expected url to be set'); + } + // Some browser and servers may not support the PATCH method. For those + // cases, you can tell tus-js-client to use a POST request with the + // X-HTTP-Method-Override header for simulating a PATCH request. + if (this.options.overridePatchMethod) { + req = this._openRequest('POST', this.url); + req.setHeader('X-HTTP-Method-Override', 'PATCH'); + } + else { + req = this._openRequest('PATCH', this.url); + } + req.setHeader('Upload-Offset', `${this._offset}`); + let res; + try { + res = await this._addChunkToRequest(req); + } + catch (err) { + // Don't emit an error if the upload was aborted manually + if (this._aborted) { + return; + } + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + throw new DetailedError_js_1.DetailedError(`tus: failed to upload chunk at offset ${this._offset}`, err, req, undefined); + } + if (!inStatusCategory(res.getStatus(), 200)) { + throw new DetailedError_js_1.DetailedError('tus: unexpected response while uploading chunk', undefined, req, res); + } + await this._handleUploadResponse(req, res); + } + /** + * _addChunktoRequest reads a chunk from the source and sends it using the + * supplied request object. It will not handle the response. + * + * @api private + */ + async _addChunkToRequest(req) { + const start = this._offset; + let end = this._offset + this.options.chunkSize; + req.setProgressHandler((bytesSent) => { + this._emitProgress(start + bytesSent, this._size); + }); + if (this.options.protocol === options_js_1.PROTOCOL_TUS_V1) { + req.setHeader('Content-Type', 'application/offset+octet-stream'); + } + else if (this.options.protocol === options_js_1.PROTOCOL_IETF_DRAFT_05) { + req.setHeader('Content-Type', 'application/partial-upload'); + } + // The specified chunkSize may be Infinity or the calcluated end position + // may exceed the file's size. In both cases, we limit the end position to + // the input's total size for simpler calculations and correctness. + if ( + // @ts-expect-error _size is set here + (end === Number.POSITIVE_INFINITY || end > this._size) && + !this._uploadLengthDeferred) { + // @ts-expect-error _size is set here + end = this._size; + } + // TODO: What happens if abort is called during slice? + // @ts-expect-error _source is set here + const { value, size, done } = await this._source.slice(start, end); + const sizeOfValue = size !== null && size !== void 0 ? size : 0; + // If the upload length is deferred, the upload size was not specified during + // upload creation. So, if the file reader is done reading, we know the total + // upload size and can tell the tus server. + if (this._uploadLengthDeferred && done) { + this._size = this._offset + sizeOfValue; + req.setHeader('Upload-Length', `${this._size}`); + this._uploadLengthDeferred = false; + } + // The specified uploadSize might not match the actual amount of data that a source + // provides. In these cases, we cannot successfully complete the upload, so we + // rather error out and let the user know. If not, tus-js-client will be stuck + // in a loop of repeating empty PATCH requests. + // See https://community.transloadit.com/t/how-to-abort-hanging-companion-uploads/16488/13 + const newSize = this._offset + sizeOfValue; + if (!this._uploadLengthDeferred && done && newSize !== this._size) { + throw new Error(`upload was configured with a size of ${this._size} bytes, but the source is done after ${newSize} bytes`); + } + if (value == null) { + return await this._sendRequest(req); + } + if (this.options.protocol === options_js_1.PROTOCOL_IETF_DRAFT_03 || + this.options.protocol === options_js_1.PROTOCOL_IETF_DRAFT_05) { + req.setHeader('Upload-Complete', done ? '?1' : '?0'); + } + this._emitProgress(this._offset, this._size); + return await this._sendRequest(req, value); + } + /** + * _handleUploadResponse is used by requests that haven been sent using _addChunkToRequest + * and already have received a response. + * + * @api private + */ + async _handleUploadResponse(req, res) { + // TODO: || '' is not very good. + const offset = Number.parseInt(res.getHeader('Upload-Offset') || '', 10); + if (Number.isNaN(offset)) { + throw new DetailedError_js_1.DetailedError('tus: invalid or missing offset value', undefined, req, res); + } + this._emitProgress(offset, this._size); + this._emitChunkComplete(offset - this._offset, offset, this._size); + this._offset = offset; + if (offset === this._size) { + // Yay, finally done :) + await this._emitSuccess(res); + if (this._source) + this._source.close(); + return; + } + await this._performUpload(); + } + /** + * Create a new HTTP request object with the given method and URL. + * + * @api private + */ + _openRequest(method, url) { + const req = openRequest(method, url, this.options); + this._req = req; + return req; + } + /** + * Remove the entry in the URL storage, if it has been saved before. + * + * @api private + */ + async _removeFromUrlStorage() { + if (!this._urlStorageKey) + return; + await this.options.urlStorage.removeUpload(this._urlStorageKey); + this._urlStorageKey = undefined; + } + /** + * Add the upload URL to the URL storage, if possible. + * + * @api private + */ + async _saveUploadInUrlStorage() { + // We do not store the upload URL + // - if it was disabled in the option, or + // - if no fingerprint was calculated for the input (i.e. a stream), or + // - if the URL is already stored (i.e. key is set alread). + if (!this.options.storeFingerprintForResuming || + !this._fingerprint || + this._urlStorageKey != null) { + return; + } + const storedUpload = { + size: this._size, + metadata: this.options.metadata, + creationTime: new Date().toString(), + urlStorageKey: this._fingerprint, + }; + if (this._parallelUploads) { + // Save multiple URLs if the parallelUploads option is used ... + storedUpload.parallelUploadUrls = this._parallelUploadUrls; + } + else { + // ... otherwise we just save the one available URL. + // @ts-expect-error We still have to figure out the null/undefined situation. + storedUpload.uploadUrl = this.url; + } + const urlStorageKey = await this.options.urlStorage.addUpload(this._fingerprint, storedUpload); + // TODO: Emit a waring if urlStorageKey is undefined. Should we even allow this? + this._urlStorageKey = urlStorageKey; + } + /** + * Send a request with the provided body. + * + * @api private + */ + _sendRequest(req, body) { + return sendRequest(req, body, this.options); + } +} +exports.BaseUpload = BaseUpload; +function encodeMetadata(metadata) { + return Object.entries(metadata) + .map(([key, value]) => `${key} ${js_base64_1.Base64.encode(String(value))}`) + .join(','); +} +/** + * Checks whether a given status is in the range of the expected category. + * For example, only a status between 200 and 299 will satisfy the category 200. + * + * @api private + */ +function inStatusCategory(status, category) { + return status >= category && status < category + 100; +} +/** + * Create a new HTTP request with the specified method and URL. + * The necessary headers that are included in every request + * will be added, including the request ID. + * + * @api private + */ +function openRequest(method, url, options) { + const req = options.httpStack.createRequest(method, url); + if (options.protocol === options_js_1.PROTOCOL_IETF_DRAFT_03) { + req.setHeader('Upload-Draft-Interop-Version', '5'); + } + else if (options.protocol === options_js_1.PROTOCOL_IETF_DRAFT_05) { + req.setHeader('Upload-Draft-Interop-Version', '6'); + } + else { + req.setHeader('Tus-Resumable', '1.0.0'); + } + const headers = options.headers || {}; + for (const [name, value] of Object.entries(headers)) { + req.setHeader(name, value); + } + if (options.addRequestId) { + const requestId = (0, uuid_js_1.uuid)(); + req.setHeader('X-Request-ID', requestId); + } + return req; +} +/** + * Send a request with the provided body while invoking the onBeforeRequest + * and onAfterResponse callbacks. + * + * @api private + */ +async function sendRequest(req, body, options) { + if (typeof options.onBeforeRequest === 'function') { + await options.onBeforeRequest(req); + } + const res = await req.send(body); + if (typeof options.onAfterResponse === 'function') { + await options.onAfterResponse(req, res); + } + return res; +} +/** + * Checks whether the browser running this code has internet access. + * This function will always return true in the node.js environment + * TODO: Move this into a browser-specific location. + * + * @api private + */ +function isOnline() { + let online = true; + // Note: We don't reference `window` here because the navigator object also exists + // in a Web Worker's context. + // -disable-next-line no-undef + if (typeof navigator !== 'undefined' && navigator.onLine === false) { + online = false; + } + return online; +} +/** + * Checks whether or not it is ok to retry a request. + * @param {Error|DetailedError} err the error returned from the last request + * @param {number} retryAttempt the number of times the request has already been retried + * @param {object} options tus Upload options + * + * @api private + */ +function shouldRetry(err, retryAttempt, options) { + // We only attempt a retry if + // - retryDelays option is set + // - we didn't exceed the maxium number of retries, yet, and + // - this error was caused by a request or it's response and + // - the error is server error (i.e. not a status 4xx except a 409 or 423) or + // a onShouldRetry is specified and returns true + // - the browser does not indicate that we are offline + const isNetworkError = 'originalRequest' in err && err.originalRequest != null; + if (options.retryDelays == null || + retryAttempt >= options.retryDelays.length || + !isNetworkError) { + return false; + } + if (options && typeof options.onShouldRetry === 'function') { + return options.onShouldRetry(err, retryAttempt, options); + } + return defaultOnShouldRetry(err); +} +/** + * determines if the request should be retried. Will only retry if not a status 4xx except a 409 or 423 + * @param {DetailedError} err + * @returns {boolean} + */ +function defaultOnShouldRetry(err) { + const status = err.originalResponse ? err.originalResponse.getStatus() : 0; + return (!inStatusCategory(status, 400) || status === 409 || status === 423) && isOnline(); +} +/** + * Resolve a relative link given the origin as source. For example, + * if a HTTP request to http://example.com/files/ returns a Location + * header with the value /upload/abc, the resolved URL will be: + * http://example.com/upload/abc + */ +function resolveUrl(origin, link) { + return new url_parse_1.default(link, origin).toString(); +} +/** + * Calculate the start and end positions for the parts if an upload + * is split into multiple parallel requests. + * + * @param {number} totalSize The byte size of the upload, which will be split. + * @param {number} partCount The number in how many parts the upload will be split. + * @return {Part[]} + * @api private + */ +function splitSizeIntoParts(totalSize, partCount) { + const partSize = Math.floor(totalSize / partCount); + const parts = []; + for (let i = 0; i < partCount; i++) { + parts.push({ + start: partSize * i, + end: partSize * (i + 1), + }); + } + parts[partCount - 1].end = totalSize; + return parts; +} +async function wait(delay) { + return new Promise((resolve) => { + setTimeout(resolve, delay); + }); +} +/** + * Use the Termination extension to delete an upload from the server by sending a DELETE + * request to the specified upload URL. This is only possible if the server supports the + * Termination extension. If the `options.retryDelays` property is set, the method will + * also retry if an error ocurrs. + * + * @param {String} url The upload's URL which will be terminated. + * @param {object} options Optional options for influencing HTTP requests. + * @return {Promise} The Promise will be resolved/rejected when the requests finish. + */ +async function terminate(url, options) { + const req = openRequest('DELETE', url, options); + try { + const res = await sendRequest(req, undefined, options); + // A 204 response indicates a successfull request + if (res.getStatus() === 204) { + return; + } + throw new DetailedError_js_1.DetailedError('tus: unexpected response while terminating upload', undefined, req, res); + } + catch (err) { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + const detailedErr = err instanceof DetailedError_js_1.DetailedError + ? err + : new DetailedError_js_1.DetailedError('tus: failed to terminate upload', err, req); + if (!shouldRetry(detailedErr, 0, options)) { + throw detailedErr; + } + // Instead of keeping track of the retry attempts, we remove the first element from the delays + // array. If the array is empty, all retry attempts are used up and we will bubble up the error. + // We recursively call the terminate function will removing elements from the retryDelays array. + const delay = options.retryDelays[0]; + const remainingDelays = options.retryDelays.slice(1); + const newOptions = { + ...options, + retryDelays: remainingDelays, + }; + await wait(delay); + await terminate(url, newOptions); + } +} +//# sourceMappingURL=upload.js.map \ No newline at end of file diff --git a/lib.cjs/upload.js.map b/lib.cjs/upload.js.map new file mode 100644 index 00000000..dc9dac12 --- /dev/null +++ b/lib.cjs/upload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"upload.js","sourceRoot":"","sources":["../lib/upload.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.cjs/uuid.d.ts b/lib.cjs/uuid.d.ts new file mode 100644 index 00000000..1866c309 --- /dev/null +++ b/lib.cjs/uuid.d.ts @@ -0,0 +1,13 @@ +/** + * Generate a UUID v4 based on random numbers. We intentioanlly use the less + * secure Math.random function here since the more secure crypto.getRandomNumbers + * is not available on all platforms. + * This is not a problem for us since we use the UUID only for generating a + * request ID, so we can correlate server logs to client errors. + * + * This function is taken from following site: + * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript + * + * @return {string} The generate UUID + */ +export declare function uuid(): string; diff --git a/lib.cjs/uuid.js b/lib.cjs/uuid.js new file mode 100644 index 00000000..99c70263 --- /dev/null +++ b/lib.cjs/uuid.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.uuid = uuid; +/** + * Generate a UUID v4 based on random numbers. We intentioanlly use the less + * secure Math.random function here since the more secure crypto.getRandomNumbers + * is not available on all platforms. + * This is not a problem for us since we use the UUID only for generating a + * request ID, so we can correlate server logs to client errors. + * + * This function is taken from following site: + * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript + * + * @return {string} The generate UUID + */ +function uuid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} +//# sourceMappingURL=uuid.js.map \ No newline at end of file diff --git a/lib.cjs/uuid.js.map b/lib.cjs/uuid.js.map new file mode 100644 index 00000000..6465bcbf --- /dev/null +++ b/lib.cjs/uuid.js.map @@ -0,0 +1 @@ +{"version":3,"file":"uuid.js","sourceRoot":"","sources":["../lib/uuid.ts"],"names":[],"mappings":";;AAYA,oBAMC;AAlBD;;;;;;;;;;;GAWG;AACH,SAAgB,IAAI;IAClB,OAAO,sCAAsC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;QACnE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAA;QACzC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC"} \ No newline at end of file diff --git a/lib.esm/DetailedError.d.ts b/lib.esm/DetailedError.d.ts new file mode 100644 index 00000000..584c9483 --- /dev/null +++ b/lib.esm/DetailedError.d.ts @@ -0,0 +1,7 @@ +import type { HttpRequest, HttpResponse } from './options.js'; +export declare class DetailedError extends Error { + originalRequest?: HttpRequest; + originalResponse?: HttpResponse; + causingError?: Error; + constructor(message: string, causingErr?: Error, req?: HttpRequest, res?: HttpResponse); +} diff --git a/lib.esm/DetailedError.js b/lib.esm/DetailedError.js new file mode 100644 index 00000000..cca41bbb --- /dev/null +++ b/lib.esm/DetailedError.js @@ -0,0 +1,21 @@ +export class DetailedError extends Error { + constructor(message, causingErr, req, res) { + super(message); + this.originalRequest = req; + this.originalResponse = res; + this.causingError = causingErr; + if (causingErr != null) { + message += `, caused by ${causingErr.toString()}`; + } + if (req != null) { + const requestId = req.getHeader('X-Request-ID') || 'n/a'; + const method = req.getMethod(); + const url = req.getURL(); + const status = res ? res.getStatus() : 'n/a'; + const body = res ? res.getBody() || '' : 'n/a'; + message += `, originated from request (method: ${method}, url: ${url}, response code: ${status}, response text: ${body}, request id: ${requestId})`; + } + this.message = message; + } +} +//# sourceMappingURL=DetailedError.js.map \ No newline at end of file diff --git a/lib.esm/DetailedError.js.map b/lib.esm/DetailedError.js.map new file mode 100644 index 00000000..494d9f31 --- /dev/null +++ b/lib.esm/DetailedError.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DetailedError.js","sourceRoot":"","sources":["../lib/DetailedError.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAOtC,YAAY,OAAe,EAAE,UAAkB,EAAE,GAAiB,EAAE,GAAkB;QACpF,KAAK,CAAC,OAAO,CAAC,CAAA;QAEd,IAAI,CAAC,eAAe,GAAG,GAAG,CAAA;QAC1B,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAA;QAC3B,IAAI,CAAC,YAAY,GAAG,UAAU,CAAA;QAE9B,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACvB,OAAO,IAAI,eAAe,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAA;QACnD,CAAC;QAED,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,KAAK,CAAA;YACxD,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAA;YAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,EAAE,CAAA;YACxB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;YAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;YAC9C,OAAO,IAAI,sCAAsC,MAAM,UAAU,GAAG,oBAAoB,MAAM,oBAAoB,IAAI,iBAAiB,SAAS,GAAG,CAAA;QACrJ,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;CACF"} \ No newline at end of file diff --git a/lib.esm/NoopUrlStorage.d.ts b/lib.esm/NoopUrlStorage.d.ts new file mode 100644 index 00000000..1329cc56 --- /dev/null +++ b/lib.esm/NoopUrlStorage.d.ts @@ -0,0 +1,7 @@ +import type { PreviousUpload, UrlStorage } from './options.js'; +export declare class NoopUrlStorage implements UrlStorage { + findAllUploads(): Promise; + findUploadsByFingerprint(_fingerprint: string): Promise; + removeUpload(_urlStorageKey: string): Promise; + addUpload(_urlStorageKey: string, _upload: PreviousUpload): Promise; +} diff --git a/lib.esm/NoopUrlStorage.js b/lib.esm/NoopUrlStorage.js new file mode 100644 index 00000000..9dfff7b6 --- /dev/null +++ b/lib.esm/NoopUrlStorage.js @@ -0,0 +1,15 @@ +export class NoopUrlStorage { + findAllUploads() { + return Promise.resolve([]); + } + findUploadsByFingerprint(_fingerprint) { + return Promise.resolve([]); + } + removeUpload(_urlStorageKey) { + return Promise.resolve(); + } + addUpload(_urlStorageKey, _upload) { + return Promise.resolve(undefined); + } +} +//# sourceMappingURL=NoopUrlStorage.js.map \ No newline at end of file diff --git a/lib.esm/NoopUrlStorage.js.map b/lib.esm/NoopUrlStorage.js.map new file mode 100644 index 00000000..3ac7f99c --- /dev/null +++ b/lib.esm/NoopUrlStorage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NoopUrlStorage.js","sourceRoot":"","sources":["../lib/NoopUrlStorage.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,cAAc;IACzB,cAAc;QACZ,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC;IAED,wBAAwB,CAAC,YAAoB;QAC3C,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC5B,CAAC;IAED,YAAY,CAAC,cAAsB;QACjC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;IAC1B,CAAC;IAED,SAAS,CAAC,cAAsB,EAAE,OAAuB;QACvD,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACnC,CAAC;CACF"} \ No newline at end of file diff --git a/lib.esm/browser/BrowserFileReader.d.ts b/lib.esm/browser/BrowserFileReader.d.ts new file mode 100644 index 00000000..155fc24c --- /dev/null +++ b/lib.esm/browser/BrowserFileReader.d.ts @@ -0,0 +1,4 @@ +import type { FileReader, FileSource, UploadInput } from '../options.js'; +export declare class BrowserFileReader implements FileReader { + openFile(input: UploadInput, chunkSize: number): Promise; +} diff --git a/lib.esm/browser/BrowserFileReader.js b/lib.esm/browser/BrowserFileReader.js new file mode 100644 index 00000000..da4a6602 --- /dev/null +++ b/lib.esm/browser/BrowserFileReader.js @@ -0,0 +1,29 @@ +import { isReactNativeFile, isReactNativePlatform } from '../reactnative/isReactNative.js'; +import { uriToBlob } from '../reactnative/uriToBlob.js'; +import { openFile as openBaseFile, supportedTypes as supportedBaseTypes, } from '../commonFileReader.js'; +import { BlobFileSource } from '../sources/BlobFileSource.js'; +export class BrowserFileReader { + 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 (isReactNativeFile(input)) { + if (!isReactNativePlatform()) { + throw new Error('tus: file objects with `uri` property is only supported in React Native'); + } + try { + const blob = await uriToBlob(input.uri); + return new BlobFileSource(blob); + } + catch (err) { + throw new Error(`tus: cannot fetch \`file.uri\` as Blob, make sure the uri is correct and accessible. ${err}`); + } + } + const fileSource = openBaseFile(input, chunkSize); + if (fileSource) + return fileSource; + throw new Error(`in this environment the source object may only be an instance of: ${supportedBaseTypes.join(', ')}`); + } +} +//# sourceMappingURL=BrowserFileReader.js.map \ No newline at end of file diff --git a/lib.esm/browser/BrowserFileReader.js.map b/lib.esm/browser/BrowserFileReader.js.map new file mode 100644 index 00000000..b60a3c32 --- /dev/null +++ b/lib.esm/browser/BrowserFileReader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"BrowserFileReader.js","sourceRoot":"","sources":["../../lib/browser/BrowserFileReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAC1F,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAEvD,OAAO,EACL,QAAQ,IAAI,YAAY,EACxB,cAAc,IAAI,kBAAkB,GACrC,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAE7D,MAAM,OAAO,iBAAiB;IAC5B,KAAK,CAAC,QAAQ,CAAC,KAAkB,EAAE,SAAiB;QAClD,wEAAwE;QACxE,qEAAqE;QACrE,2DAA2D;QAC3D,4CAA4C;QAC5C,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAA;YAC5F,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;gBACvC,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,CAAA;YACjC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACb,wFAAwF,GAAG,EAAE,CAC9F,CAAA;YACH,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QACjD,IAAI,UAAU;YAAE,OAAO,UAAU,CAAA;QAEjC,MAAM,IAAI,KAAK,CACb,qEAAqE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACrG,CAAA;IACH,CAAC;CACF"} \ No newline at end of file diff --git a/lib.esm/browser/FetchHttpStack.d.ts b/lib.esm/browser/FetchHttpStack.d.ts new file mode 100644 index 00000000..7f77e9b9 --- /dev/null +++ b/lib.esm/browser/FetchHttpStack.d.ts @@ -0,0 +1,30 @@ +import type { HttpProgressHandler, HttpRequest, HttpResponse, HttpStack, SliceType } from '../options.js'; +export declare class FetchHttpStack implements HttpStack { + createRequest(method: string, url: string): FetchRequest; + getName(): string; +} +declare class FetchRequest implements HttpRequest { + private _method; + private _url; + private _headers; + private _controller; + constructor(method: string, url: string); + getMethod(): string; + getURL(): string; + setHeader(header: string, value: string): void; + getHeader(header: string): string; + setProgressHandler(_progressHandler: HttpProgressHandler): void; + send(body?: SliceType): Promise; + abort(): Promise; + getUnderlyingObject(): undefined; +} +declare class FetchResponse implements HttpResponse { + private _res; + private _body; + constructor(res: Response, body: string); + getStatus(): number; + getHeader(header: string): string | undefined; + getBody(): string; + getUnderlyingObject(): Response; +} +export {}; diff --git a/lib.esm/browser/FetchHttpStack.js b/lib.esm/browser/FetchHttpStack.js new file mode 100644 index 00000000..c3e76cef --- /dev/null +++ b/lib.esm/browser/FetchHttpStack.js @@ -0,0 +1,73 @@ +import { readable as isNodeReadableStream } from 'is-stream'; +// TODO: Add tests for this. +export class FetchHttpStack { + createRequest(method, url) { + return new FetchRequest(method, url); + } + getName() { + return 'FetchHttpStack'; + } +} +class FetchRequest { + constructor(method, url) { + this._headers = {}; + this._controller = new AbortController(); + this._method = method; + this._url = url; + } + getMethod() { + return this._method; + } + getURL() { + return this._url; + } + setHeader(header, value) { + this._headers[header] = value; + } + getHeader(header) { + return this._headers[header]; + } + setProgressHandler(_progressHandler) { + // The Fetch API currently does not expose a way to track upload progress. + } + async send(body) { + if (isNodeReadableStream(body)) { + throw new Error('Using a Node.js readable stream as HTTP request body is not supported using the Fetch API HTTP stack.'); + } + const res = await fetch(this._url, { + method: this._method, + headers: this._headers, + body, + signal: this._controller.signal, + }); + const resBody = await res.text(); + return new FetchResponse(res, resBody); + } + abort() { + this._controller.abort(); + return Promise.resolve(); + } + getUnderlyingObject() { + // In the Fetch API, there is no object representing the request. + return undefined; + } +} +class FetchResponse { + constructor(res, body) { + this._res = res; + this._body = body; + } + getStatus() { + return this._res.status; + } + getHeader(header) { + return this._res.headers.get(header) || undefined; + } + getBody() { + return this._body; + } + getUnderlyingObject() { + return this._res; + } +} +//# sourceMappingURL=FetchHttpStack.js.map \ No newline at end of file diff --git a/lib.esm/browser/FetchHttpStack.js.map b/lib.esm/browser/FetchHttpStack.js.map new file mode 100644 index 00000000..749e6a6f --- /dev/null +++ b/lib.esm/browser/FetchHttpStack.js.map @@ -0,0 +1 @@ +{"version":3,"file":"FetchHttpStack.js","sourceRoot":"","sources":["../../lib/browser/FetchHttpStack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAS5D,4BAA4B;AAC5B,MAAM,OAAO,cAAc;IACzB,aAAa,CAAC,MAAc,EAAE,GAAW;QACvC,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,CAAC;IAED,OAAO;QACL,OAAO,gBAAgB,CAAA;IACzB,CAAC;CACF;AAED,MAAM,YAAY;IAMhB,YAAY,MAAc,EAAE,GAAW;QAH/B,aAAQ,GAA2B,EAAE,CAAA;QACrC,gBAAW,GAAG,IAAI,eAAe,EAAE,CAAA;QAGzC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAA;IACjB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,SAAS,CAAC,MAAc,EAAE,KAAa;QACrC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,KAAK,CAAA;IAC/B,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9B,CAAC;IAED,kBAAkB,CAAC,gBAAqC;QACtD,0EAA0E;IAC5E,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAgB;QACzB,IAAI,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CACb,uGAAuG,CACxG,CAAA;QACH,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;YACjC,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,IAAI;YACJ,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM;SAChC,CAAC,CAAA;QAEF,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAChC,OAAO,IAAI,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACxC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAA;QACxB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;IAC1B,CAAC;IAED,mBAAmB;QACjB,iEAAiE;QACjE,OAAO,SAAS,CAAA;IAClB,CAAC;CACF;AAED,MAAM,aAAa;IAIjB,YAAY,GAAa,EAAE,IAAY;QACrC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAA;QACf,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAA;IACzB,CAAC;IAED,SAAS,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS,CAAA;IACnD,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,mBAAmB;QACjB,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;CACF"} \ No newline at end of file diff --git a/lib.esm/browser/XHRHttpStack.d.ts b/lib.esm/browser/XHRHttpStack.d.ts new file mode 100644 index 00000000..59e59ed0 --- /dev/null +++ b/lib.esm/browser/XHRHttpStack.d.ts @@ -0,0 +1,5 @@ +import type { HttpRequest, HttpStack } from '../options.js'; +export declare class XHRHttpStack implements HttpStack { + createRequest(method: string, url: string): HttpRequest; + getName(): string; +} diff --git a/lib.esm/browser/XHRHttpStack.js b/lib.esm/browser/XHRHttpStack.js new file mode 100644 index 00000000..382338cb --- /dev/null +++ b/lib.esm/browser/XHRHttpStack.js @@ -0,0 +1,82 @@ +import { readable as isNodeReadableStream } from 'is-stream'; +export class XHRHttpStack { + createRequest(method, url) { + return new XHRRequest(method, url); + } + getName() { + return 'XHRHttpStack'; + } +} +class XHRRequest { + constructor(method, url) { + this._xhr = new XMLHttpRequest(); + this._headers = {}; + this._xhr.open(method, url, true); + this._method = method; + this._url = url; + } + 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) { + if (isNodeReadableStream(body)) { + throw new Error('Using a Node.js readable stream as HTTP request body is not supported using the XMLHttpRequest HTTP stack.'); + } + return new Promise((resolve, reject) => { + this._xhr.onload = () => { + resolve(new XHRResponse(this._xhr)); + }; + this._xhr.onerror = (err) => { + reject(err); + }; + this._xhr.send(body); + }); + } + abort() { + this._xhr.abort(); + return Promise.resolve(); + } + getUnderlyingObject() { + return this._xhr; + } +} +class XHRResponse { + constructor(xhr) { + this._xhr = xhr; + } + getStatus() { + return this._xhr.status; + } + getHeader(header) { + return this._xhr.getResponseHeader(header) || undefined; + } + getBody() { + return this._xhr.responseText; + } + getUnderlyingObject() { + return this._xhr; + } +} +//# sourceMappingURL=XHRHttpStack.js.map \ No newline at end of file diff --git a/lib.esm/browser/XHRHttpStack.js.map b/lib.esm/browser/XHRHttpStack.js.map new file mode 100644 index 00000000..d4299d02 --- /dev/null +++ b/lib.esm/browser/XHRHttpStack.js.map @@ -0,0 +1 @@ +{"version":3,"file":"XHRHttpStack.js","sourceRoot":"","sources":["../../lib/browser/XHRHttpStack.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.esm/browser/fileSignature.d.ts b/lib.esm/browser/fileSignature.d.ts new file mode 100644 index 00000000..36d136c1 --- /dev/null +++ b/lib.esm/browser/fileSignature.d.ts @@ -0,0 +1,5 @@ +import type { UploadInput, UploadOptions } from '../options.js'; +/** + * Generate a fingerprint for a file which will be used the store the endpoint + */ +export declare function fingerprint(file: UploadInput, options: UploadOptions): Promise | Promise; diff --git a/lib.esm/browser/fileSignature.js b/lib.esm/browser/fileSignature.js new file mode 100644 index 00000000..b717bd64 --- /dev/null +++ b/lib.esm/browser/fileSignature.js @@ -0,0 +1,34 @@ +import { isReactNativeFile, isReactNativePlatform } from '../reactnative/isReactNative.js'; +/** + * Generate a fingerprint for a file which will be used the store the endpoint + */ +export function fingerprint(file, options) { + if (isReactNativePlatform() && isReactNativeFile(file)) { + return Promise.resolve(reactNativeFingerprint(file, options)); + } + if (file instanceof Blob) { + return Promise.resolve( + //@ts-expect-error TODO: We have to check the input type here + // This can be fixed by moving the fingerprint function to the FileReader class + ['tus-br', file.name, file.type, file.size, file.lastModified, options.endpoint].join('-')); + } + return Promise.resolve(null); +} +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; +} +//# sourceMappingURL=fileSignature.js.map \ No newline at end of file diff --git a/lib.esm/browser/fileSignature.js.map b/lib.esm/browser/fileSignature.js.map new file mode 100644 index 00000000..edd70d7e --- /dev/null +++ b/lib.esm/browser/fileSignature.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fileSignature.js","sourceRoot":"","sources":["../../lib/browser/fileSignature.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAE1F;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAiB,EAAE,OAAsB;IACnE,IAAI,qBAAqB,EAAE,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,OAAO,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IAC/D,CAAC;IAED,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,OAAO;QACpB,6DAA6D;QAC7D,+EAA+E;QAC/E,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAC3F,CAAA;IACH,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAC9B,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAqB,EAAE,OAAsB;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IAC3E,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAC9F,GAAG,CACJ,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,kDAAkD;IAClD,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;QAChC,IAAI,IAAI,IAAI,CAAA,CAAC,2BAA2B;IAC1C,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"} \ No newline at end of file diff --git a/lib.esm/browser/index.d.ts b/lib.esm/browser/index.d.ts new file mode 100644 index 00000000..cc4b180f --- /dev/null +++ b/lib.esm/browser/index.d.ts @@ -0,0 +1,47 @@ +import { DetailedError } from '../DetailedError.js'; +import { NoopUrlStorage } from '../NoopUrlStorage.js'; +import { enableDebugLog } from '../logger.js'; +import type { UploadInput, UploadOptions } from '../options.js'; +import { BaseUpload } from '../upload.js'; +import { BrowserFileReader } from './BrowserFileReader.js'; +import { XHRHttpStack as DefaultHttpStack } from './XHRHttpStack.js'; +import { fingerprint } from './fileSignature.js'; +import { WebStorageUrlStorage, canStoreURLs } from './urlStorage.js'; +declare const defaultOptions: { + httpStack: DefaultHttpStack; + fileReader: BrowserFileReader; + urlStorage: NoopUrlStorage | WebStorageUrlStorage; + fingerprint: typeof fingerprint; + endpoint: undefined; + uploadUrl: undefined; + metadata: {}; + metadataForPartialUploads: {}; + uploadSize: undefined; + onProgress: undefined; + onChunkComplete: undefined; + onSuccess: undefined; + onError: undefined; + onUploadUrlAvailable: undefined; + overridePatchMethod: boolean; + headers: {}; + addRequestId: boolean; + onBeforeRequest: undefined; + onAfterResponse: undefined; + onShouldRetry: (err: DetailedError) => boolean; + chunkSize: number; + retryDelays: number[]; + parallelUploads: number; + parallelUploadBoundaries: undefined; + storeFingerprintForResuming: boolean; + removeFingerprintOnSuccess: boolean; + uploadLengthDeferred: boolean; + uploadDataDuringCreation: boolean; + protocol: UploadOptions["protocol"]; +}; +declare class Upload extends BaseUpload { + constructor(file: UploadInput, options?: Partial); + static terminate(url: string, options?: Partial): Promise; +} +declare const isSupported: boolean; +export { Upload, defaultOptions, isSupported, canStoreURLs, enableDebugLog, DetailedError }; +export type * from '../options.js'; diff --git a/lib.esm/browser/index.js b/lib.esm/browser/index.js new file mode 100644 index 00000000..87cb68a4 --- /dev/null +++ b/lib.esm/browser/index.js @@ -0,0 +1,33 @@ +import { DetailedError } from '../DetailedError.js'; +import { NoopUrlStorage } from '../NoopUrlStorage.js'; +import { enableDebugLog } from '../logger.js'; +import { BaseUpload, defaultOptions as baseDefaultOptions, terminate } from '../upload.js'; +import { BrowserFileReader } from './BrowserFileReader.js'; +import { XHRHttpStack as DefaultHttpStack } from './XHRHttpStack.js'; +import { fingerprint } from './fileSignature.js'; +import { WebStorageUrlStorage, canStoreURLs } from './urlStorage.js'; +const defaultOptions = { + ...baseDefaultOptions, + httpStack: new DefaultHttpStack(), + fileReader: new BrowserFileReader(), + urlStorage: canStoreURLs ? new WebStorageUrlStorage() : new NoopUrlStorage(), + fingerprint, +}; +class Upload extends BaseUpload { + constructor(file, options = {}) { + const allOpts = { ...defaultOptions, ...options }; + super(file, allOpts); + } + static terminate(url, options = {}) { + const allOpts = { ...defaultOptions, ...options }; + return terminate(url, allOpts); + } +} +// 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'; +// Note: The exported interface must be the same as in lib/node/index.ts. +// Any changes should be reflected in both files. +export { Upload, defaultOptions, isSupported, canStoreURLs, enableDebugLog, DetailedError }; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib.esm/browser/index.js.map b/lib.esm/browser/index.js.map new file mode 100644 index 00000000..01adaba5 --- /dev/null +++ b/lib.esm/browser/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,UAAU,EAAE,cAAc,IAAI,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEpE,MAAM,cAAc,GAAG;IACrB,GAAG,kBAAkB;IACrB,SAAS,EAAE,IAAI,gBAAgB,EAAE;IACjC,UAAU,EAAE,IAAI,iBAAiB,EAAE;IACnC,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC,CAAC,IAAI,cAAc,EAAE;IAC5E,WAAW;CACZ,CAAA;AAED,MAAM,MAAO,SAAQ,UAAU;IAC7B,YAAY,IAAiB,EAAE,UAAkC,EAAE;QACjE,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAA;QACjD,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACtB,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,GAAW,EAAE,UAAkC,EAAE;QAChE,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAA;QACjD,OAAO,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChC,CAAC;CACF;AAED,qGAAqG;AACrG,MAAM,WAAW,GACf,OAAO,cAAc,KAAK,UAAU;IACpC,OAAO,IAAI,KAAK,UAAU;IAC1B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,UAAU,CAAA;AAE5C,yEAAyE;AACzE,iDAAiD;AACjD,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA"} \ No newline at end of file diff --git a/lib.esm/browser/urlStorage.d.ts b/lib.esm/browser/urlStorage.d.ts new file mode 100644 index 00000000..8714c2cf --- /dev/null +++ b/lib.esm/browser/urlStorage.d.ts @@ -0,0 +1,9 @@ +import type { PreviousUpload, UrlStorage } from '../options.js'; +export declare const canStoreURLs: boolean; +export declare class WebStorageUrlStorage implements UrlStorage { + findAllUploads(): Promise; + findUploadsByFingerprint(fingerprint: string): Promise; + removeUpload(urlStorageKey: string): Promise; + addUpload(fingerprint: string, upload: PreviousUpload): Promise; + private _findEntries; +} diff --git a/lib.esm/browser/urlStorage.js b/lib.esm/browser/urlStorage.js new file mode 100644 index 00000000..5c991f7c --- /dev/null +++ b/lib.esm/browser/urlStorage.js @@ -0,0 +1,75 @@ +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, String(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) + // TODO: Replace `code` with `name` + if (e instanceof DOMException && (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 == null) { + throw new Error(`didn't find key for item ${i}`); + } + // Ignore entires that are not from tus-js-client + if (key.indexOf(prefix) !== 0) + continue; + const item = localStorage.getItem(key); + if (item == null) { + throw new Error(`didn't find item for key ${key}`); + } + try { + // TODO: Validate JSON + const upload = JSON.parse(item); + 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; + } +} +//# sourceMappingURL=urlStorage.js.map \ No newline at end of file diff --git a/lib.esm/browser/urlStorage.js.map b/lib.esm/browser/urlStorage.js.map new file mode 100644 index 00000000..d1544aee --- /dev/null +++ b/lib.esm/browser/urlStorage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"urlStorage.js","sourceRoot":"","sources":["../../lib/browser/urlStorage.ts"],"names":[],"mappings":"AAEA,IAAI,UAAU,GAAG,KAAK,CAAA;AACtB,IAAI,CAAC;IACH,6FAA6F;IAC7F,UAAU,GAAG,cAAc,IAAI,MAAM,CAAA;IAErC,6EAA6E;IAC7E,kCAAkC;IAClC,2EAA2E;IAC3E,iDAAiD;IACjD,MAAM,GAAG,GAAG,YAAY,CAAA;IACxB,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC/C,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC,CAAA;IAChD,IAAI,aAAa,IAAI,IAAI;QAAE,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;AACzD,CAAC;AAAC,OAAO,CAAU,EAAE,CAAC;IACpB,8EAA8E;IAC9E,yEAAyE;IACzE,mBAAmB;IACnB,mCAAmC;IACnC,IAAI,CAAC,YAAY,YAAY,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAChG,UAAU,GAAG,KAAK,CAAA;IACpB,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,CAAA;IACT,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAA;AAEtC,MAAM,OAAO,oBAAoB;IAC/B,cAAc;QACZ,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;QAC1C,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACjC,CAAC;IAED,wBAAwB,CAAC,WAAmB;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,WAAW,IAAI,CAAC,CAAA;QAC1D,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACjC,CAAC;IAED,YAAY,CAAC,aAAqB;QAChC,YAAY,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;QACtC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;IAC1B,CAAC;IAED,SAAS,CAAC,WAAmB,EAAE,MAAsB;QACnD,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,QAAQ,WAAW,KAAK,EAAE,EAAE,CAAA;QAExC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAA;QACjD,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC7B,CAAC;IAEO,YAAY,CAAC,MAAc;QACjC,MAAM,OAAO,GAAqB,EAAE,CAAA;QAEpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YAC/B,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAA;YAClD,CAAC;YAED,iDAAiD;YACjD,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;gBAAE,SAAQ;YAEvC,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YACtC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,EAAE,CAAC,CAAA;YACpD,CAAC;YAED,IAAI,CAAC;gBACH,sBAAsB;gBACtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBAC/B,MAAM,CAAC,aAAa,GAAG,GAAG,CAAA;gBAE1B,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACtB,CAAC;YAAC,OAAO,EAAE,EAAE,CAAC;gBACZ,qEAAqE;gBACrE,iDAAiD;YACnD,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC;CACF"} \ No newline at end of file diff --git a/lib.esm/commonFileReader.d.ts b/lib.esm/commonFileReader.d.ts new file mode 100644 index 00000000..884e582e --- /dev/null +++ b/lib.esm/commonFileReader.d.ts @@ -0,0 +1,7 @@ +import type { FileSource, UploadInput } from './options.js'; +/** + * openFile provides FileSources for input types that have to be handled in all environments, + * including Node.js and browsers. + */ +export declare function openFile(input: UploadInput, chunkSize: number): FileSource | null; +export declare const supportedTypes: string[]; diff --git a/lib.esm/commonFileReader.js b/lib.esm/commonFileReader.js new file mode 100644 index 00000000..75271547 --- /dev/null +++ b/lib.esm/commonFileReader.js @@ -0,0 +1,46 @@ +import { ArrayBufferViewFileSource } from './sources/ArrayBufferViewFileSource.js'; +import { BlobFileSource } from './sources/BlobFileSource.js'; +import { WebStreamFileSource } from './sources/WebStreamFileSource.js'; +/** + * openFile provides FileSources for input types that have to be handled in all environments, + * including Node.js and browsers. + */ +export function openFile(input, chunkSize) { + // File is a subtype of Blob, so we only check for Blob here. + // Note: We could turn Blobs into ArrayBuffers using `input.arrayBuffer()` and then + // pass it to the ArrayBufferFileSource. However, in browsers, a File instance can + // represent a file on disk. By keeping it a File instance and passing it to XHR/Fetch, + // we can avoid reading the entire file into memory. + if (input instanceof Blob) { + return new BlobFileSource(input); + } + // ArrayBufferViews can be TypedArray (e.g. Uint8Array) or DataView instances. + // Note that Node.js' Buffers are also Uint8Arrays. + if (ArrayBuffer.isView(input)) { + return new ArrayBufferViewFileSource(input); + } + // SharedArrayBuffer is not available in all browser context for security reasons. + // Hence we check if the constructor exists at all. + if (input instanceof ArrayBuffer || + (typeof SharedArrayBuffer !== 'undefined' && input instanceof SharedArrayBuffer)) { + const view = new DataView(input); + return new ArrayBufferViewFileSource(view); + } + if (input instanceof ReadableStream) { + chunkSize = Number(chunkSize); + if (!Number.isFinite(chunkSize)) { + throw new Error('cannot create source for stream without a finite value for the `chunkSize` option'); + } + return new WebStreamFileSource(input); + } + return null; +} +export const supportedTypes = [ + 'File', + 'Blob', + 'ArrayBuffer', + 'SharedArrayBuffer', + 'ArrayBufferView', + 'ReadableStream (Web Streams)', +]; +//# sourceMappingURL=commonFileReader.js.map \ No newline at end of file diff --git a/lib.esm/commonFileReader.js.map b/lib.esm/commonFileReader.js.map new file mode 100644 index 00000000..7182cc1f --- /dev/null +++ b/lib.esm/commonFileReader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commonFileReader.js","sourceRoot":"","sources":["../lib/commonFileReader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAEtE;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAkB,EAAE,SAAiB;IAC5D,6DAA6D;IAC7D,mFAAmF;IACnF,kFAAkF;IAClF,uFAAuF;IACvF,oDAAoD;IACpD,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QAC1B,OAAO,IAAI,cAAc,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,8EAA8E;IAC9E,mDAAmD;IACnD,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,yBAAyB,CAAC,KAAK,CAAC,CAAA;IAC7C,CAAC;IAED,kFAAkF;IAClF,mDAAmD;IACnD,IACE,KAAK,YAAY,WAAW;QAC5B,CAAC,OAAO,iBAAiB,KAAK,WAAW,IAAI,KAAK,YAAY,iBAAiB,CAAC,EAChF,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAA;QAChC,OAAO,IAAI,yBAAyB,CAAC,IAAI,CAAC,CAAA;IAC5C,CAAC;IAED,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;QACpC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,mFAAmF,CACpF,CAAA;QACH,CAAC;QAED,OAAO,IAAI,mBAAmB,CAAC,KAAK,CAAC,CAAA;IACvC,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM;IACN,MAAM;IACN,aAAa;IACb,mBAAmB;IACnB,iBAAiB;IACjB,8BAA8B;CAC/B,CAAA"} \ No newline at end of file diff --git a/lib.esm/cordova/isCordova.d.ts b/lib.esm/cordova/isCordova.d.ts new file mode 100644 index 00000000..711d1d31 --- /dev/null +++ b/lib.esm/cordova/isCordova.d.ts @@ -0,0 +1,2 @@ +declare const isCordova: () => boolean; +export { isCordova }; diff --git a/lib.esm/cordova/isCordova.js b/lib.esm/cordova/isCordova.js new file mode 100644 index 00000000..b64d162c --- /dev/null +++ b/lib.esm/cordova/isCordova.js @@ -0,0 +1,4 @@ +const isCordova = () => typeof window !== 'undefined' && + ('PhoneGap' in window || 'Cordova' in window || 'cordova' in window); +export { isCordova }; +//# sourceMappingURL=isCordova.js.map \ No newline at end of file diff --git a/lib.esm/cordova/isCordova.js.map b/lib.esm/cordova/isCordova.js.map new file mode 100644 index 00000000..11215b69 --- /dev/null +++ b/lib.esm/cordova/isCordova.js.map @@ -0,0 +1 @@ +{"version":3,"file":"isCordova.js","sourceRoot":"","sources":["../../lib/cordova/isCordova.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,GAAG,EAAE,CACrB,OAAO,MAAM,KAAK,WAAW;IAC7B,CAAC,UAAU,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,CAAC,CAAA;AAEtE,OAAO,EAAE,SAAS,EAAE,CAAA"} \ No newline at end of file diff --git a/lib.esm/cordova/readAsByteArray.d.ts b/lib.esm/cordova/readAsByteArray.d.ts new file mode 100644 index 00000000..e89ab2c4 --- /dev/null +++ b/lib.esm/cordova/readAsByteArray.d.ts @@ -0,0 +1,6 @@ +/** + * readAsByteArray converts a File/Blob 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 declare function readAsByteArray(chunk: Blob): Promise; diff --git a/lib.esm/cordova/readAsByteArray.js b/lib.esm/cordova/readAsByteArray.js new file mode 100644 index 00000000..43b1045f --- /dev/null +++ b/lib.esm/cordova/readAsByteArray.js @@ -0,0 +1,25 @@ +/** + * readAsByteArray converts a File/Blob 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 + */ +// TODO: Reconsider whether this is a sensible approach or whether we cause +// high memory usage with `chunkSize` is unset. +export function readAsByteArray(chunk) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + if (!(reader.result instanceof ArrayBuffer)) { + reject(new Error(`invalid result types for readAsArrayBuffer: ${typeof reader.result}`)); + return; + } + const value = new Uint8Array(reader.result); + resolve(value); + }; + reader.onerror = (err) => { + reject(err); + }; + reader.readAsArrayBuffer(chunk); + }); +} +//# sourceMappingURL=readAsByteArray.js.map \ No newline at end of file diff --git a/lib.esm/cordova/readAsByteArray.js.map b/lib.esm/cordova/readAsByteArray.js.map new file mode 100644 index 00000000..2c6e59a1 --- /dev/null +++ b/lib.esm/cordova/readAsByteArray.js.map @@ -0,0 +1 @@ +{"version":3,"file":"readAsByteArray.js","sourceRoot":"","sources":["../../lib/cordova/readAsByteArray.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,2EAA2E;AAC3E,+CAA+C;AAC/C,MAAM,UAAU,eAAe,CAAC,KAAW;IACzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAA;QAC/B,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;YACnB,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,YAAY,WAAW,CAAC,EAAE,CAAC;gBAC5C,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,OAAO,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;gBACxF,OAAM;YACR,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC3C,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC,CAAA;QACD,MAAM,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,EAAE;YACvB,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC,CAAA;QACD,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;AACJ,CAAC"} \ No newline at end of file diff --git a/lib.esm/logger.d.ts b/lib.esm/logger.d.ts new file mode 100644 index 00000000..f6d7e036 --- /dev/null +++ b/lib.esm/logger.d.ts @@ -0,0 +1,2 @@ +export declare function enableDebugLog(): void; +export declare function log(msg: string): void; diff --git a/lib.esm/logger.js b/lib.esm/logger.js new file mode 100644 index 00000000..108d1ec7 --- /dev/null +++ b/lib.esm/logger.js @@ -0,0 +1,11 @@ +let isEnabled = false; +// TODO: Replace this global state with an option for the Upload class +export function enableDebugLog() { + isEnabled = true; +} +export function log(msg) { + if (!isEnabled) + return; + console.log(msg); +} +//# sourceMappingURL=logger.js.map \ No newline at end of file diff --git a/lib.esm/logger.js.map b/lib.esm/logger.js.map new file mode 100644 index 00000000..029b8224 --- /dev/null +++ b/lib.esm/logger.js.map @@ -0,0 +1 @@ +{"version":3,"file":"logger.js","sourceRoot":"","sources":["../lib/logger.ts"],"names":[],"mappings":"AAAA,IAAI,SAAS,GAAG,KAAK,CAAA;AAErB,sEAAsE;AACtE,MAAM,UAAU,cAAc;IAC5B,SAAS,GAAG,IAAI,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,GAAW;IAC7B,IAAI,CAAC,SAAS;QAAE,OAAM;IACtB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AAClB,CAAC"} \ No newline at end of file diff --git a/lib.esm/node/FileUrlStorage.d.ts b/lib.esm/node/FileUrlStorage.d.ts new file mode 100644 index 00000000..c22126f5 --- /dev/null +++ b/lib.esm/node/FileUrlStorage.d.ts @@ -0,0 +1,16 @@ +import type { PreviousUpload, UrlStorage } from '../options.js'; +export declare const canStoreURLs = true; +export declare class FileUrlStorage implements UrlStorage { + path: string; + constructor(filePath: string); + findAllUploads(): Promise; + findUploadsByFingerprint(fingerprint: string): Promise; + removeUpload(urlStorageKey: string): Promise; + addUpload(fingerprint: string, upload: PreviousUpload): Promise; + private _setItem; + private _getItems; + private _removeItem; + private _lockfileOptions; + private _writeData; + private _getData; +} diff --git a/lib.esm/node/FileUrlStorage.js b/lib.esm/node/FileUrlStorage.js new file mode 100644 index 00000000..1671f288 --- /dev/null +++ b/lib.esm/node/FileUrlStorage.js @@ -0,0 +1,86 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { lock } from 'proper-lockfile'; +export const canStoreURLs = true; +export class FileUrlStorage { + constructor(filePath) { + this.path = filePath; + } + async findAllUploads() { + return await this._getItems('tus::'); + } + async findUploadsByFingerprint(fingerprint) { + return await this._getItems(`tus::${fingerprint}`); + } + async removeUpload(urlStorageKey) { + await this._removeItem(urlStorageKey); + } + async addUpload(fingerprint, upload) { + const id = Math.round(Math.random() * 1e12); + const key = `tus::${fingerprint}::${id}`; + await this._setItem(key, upload); + return key; + } + async _setItem(key, value) { + const release = await lock(this.path, this._lockfileOptions()); + try { + const data = await this._getData(); + data[key] = value; + await this._writeData(data); + } + finally { + await release(); + } + } + async _getItems(prefix) { + const data = await this._getData(); + const results = Object.keys(data) + .filter((key) => key.startsWith(prefix)) + .map((key) => { + const obj = data[key]; + obj.urlStorageKey = key; + return obj; + }); + return results; + } + async _removeItem(key) { + const release = await lock(this.path, this._lockfileOptions()); + try { + const data = await this._getData(); + delete data[key]; + await this._writeData(data); + } + finally { + await release(); + } + } + _lockfileOptions() { + return { + realpath: false, + retries: { + retries: 5, + minTimeout: 20, + }, + }; + } + async _writeData(data) { + await writeFile(this.path, JSON.stringify(data), { + encoding: 'utf8', + mode: 0o660, + flag: 'w', + }); + } + async _getData() { + let data = ''; + try { + data = await readFile(this.path, 'utf8'); + } + catch (err) { + // return empty data if file does not exist + if (err != null && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') + return {}; + } + data = data.trim(); + return data.length === 0 ? {} : JSON.parse(data); + } +} +//# sourceMappingURL=FileUrlStorage.js.map \ No newline at end of file diff --git a/lib.esm/node/FileUrlStorage.js.map b/lib.esm/node/FileUrlStorage.js.map new file mode 100644 index 00000000..9d207927 --- /dev/null +++ b/lib.esm/node/FileUrlStorage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"FileUrlStorage.js","sourceRoot":"","sources":["../../lib/node/FileUrlStorage.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.esm/node/NodeFileReader.d.ts b/lib.esm/node/NodeFileReader.d.ts new file mode 100644 index 00000000..e0914cdb --- /dev/null +++ b/lib.esm/node/NodeFileReader.d.ts @@ -0,0 +1,4 @@ +import type { FileReader, UploadInput } from '../options.js'; +export declare class NodeFileReader implements FileReader { + openFile(input: UploadInput, chunkSize: number): Promise | Promise; +} diff --git a/lib.esm/node/NodeFileReader.js b/lib.esm/node/NodeFileReader.js new file mode 100644 index 00000000..7b31055b --- /dev/null +++ b/lib.esm/node/NodeFileReader.js @@ -0,0 +1,45 @@ +import { createReadStream } from 'node:fs'; +import isStream from 'is-stream'; +import { openFile as openBaseFile, supportedTypes as supportedBaseTypes, } from '../commonFileReader.js'; +import { NodeStreamFileSource } from './sources/NodeStreamFileSource.js'; +import { getFileSourceFromPath } from './sources/PathFileSource.js'; +function isPathReference(input) { + return (typeof input === 'object' && + input !== null && + 'path' in input && + (typeof input.path === 'string' || Buffer.isBuffer(input.path))); +} +export class NodeFileReader { + openFile(input, chunkSize) { + if (isPathReference(input)) { + return getFileSourceFromPath(input); + } + if (isStream.readable(input)) { + chunkSize = Number(chunkSize); + if (!Number.isFinite(chunkSize)) { + throw new Error('cannot create source for stream without a finite value for the `chunkSize` option; specify a chunkSize to control the memory consumption'); + } + return Promise.resolve(new NodeStreamFileSource(input)); + } + const fileSource = openBaseFile(input, chunkSize); + if (fileSource) + return Promise.resolve(fileSource); + throw new Error(`in this environment the source object may only be an instance of: ${supportedBaseTypes.join(', ')}, fs.ReadStream (Node.js), stream.Readable (Node.js)`); + } +} +/** + * This (unused) function is a simple test to ensure that fs.ReadStreams + * satisfy the PathReference interface. In the past, tus-js-client explicitly + * accepted fs.ReadStreams and included it in its type definitions. + * + * Since tus-js-client v5, we have moved away from only accepting fs.ReadStream + * in favor of a more generic PathReference. This function ensures that the definition + * of PathReference includes fs.ReadStream. If this wasn't the case, the TypeScript + * compiler would complain during the build step, making this a poor-man's type test. + */ +// biome-ignore lint/correctness/noUnusedVariables: see above +function testFsReadStreamAsPathReference() { + const pathReference = createReadStream('test.txt'); + new NodeFileReader().openFile(pathReference, 1024); +} +//# sourceMappingURL=NodeFileReader.js.map \ No newline at end of file diff --git a/lib.esm/node/NodeFileReader.js.map b/lib.esm/node/NodeFileReader.js.map new file mode 100644 index 00000000..72e7dab7 --- /dev/null +++ b/lib.esm/node/NodeFileReader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NodeFileReader.js","sourceRoot":"","sources":["../../lib/node/NodeFileReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,QAAQ,MAAM,WAAW,CAAA;AAEhC,OAAO,EACL,QAAQ,IAAI,YAAY,EACxB,cAAc,IAAI,kBAAkB,GACrC,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAA;AAEnE,SAAS,eAAe,CAAC,KAAkB;IACzC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAChE,CAAA;AACH,CAAC;AAED,MAAM,OAAO,cAAc;IACzB,QAAQ,CAAC,KAAkB,EAAE,SAAiB;QAC5C,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAA;QACrC,CAAC;QAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;YAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CACb,0IAA0I,CAC3I,CAAA;YACH,CAAC;YACD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAA;QACzD,CAAC;QAED,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QACjD,IAAI,UAAU;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAElD,MAAM,IAAI,KAAK,CACb,qEAAqE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,sDAAsD,CACzJ,CAAA;IACH,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,6DAA6D;AAC7D,SAAS,+BAA+B;IACtC,MAAM,aAAa,GAAkB,gBAAgB,CAAC,UAAU,CAAC,CAAA;IACjE,IAAI,cAAc,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;AACpD,CAAC"} \ No newline at end of file diff --git a/lib.esm/node/NodeHttpStack.d.ts b/lib.esm/node/NodeHttpStack.d.ts new file mode 100644 index 00000000..55a3744c --- /dev/null +++ b/lib.esm/node/NodeHttpStack.d.ts @@ -0,0 +1,26 @@ +import * as http from 'node:http'; +import type { HttpProgressHandler, HttpRequest, HttpResponse, HttpStack, SliceType } from '../options.js'; +export declare class NodeHttpStack implements HttpStack { + private _requestOptions; + constructor(requestOptions?: http.RequestOptions); + createRequest(method: string, url: string): Request; + getName(): string; +} +declare class Request implements HttpRequest { + private _method; + private _url; + private _headers; + private _request; + private _progressHandler; + private _requestOptions; + constructor(method: string, url: string, options: http.RequestOptions); + getMethod(): string; + getURL(): string; + setHeader(header: string, value: string): void; + getHeader(header: string): string; + setProgressHandler(progressHandler: HttpProgressHandler): void; + send(body?: SliceType): Promise; + abort(): Promise; + getUnderlyingObject(): http.ClientRequest | null; +} +export {}; diff --git a/lib.esm/node/NodeHttpStack.js b/lib.esm/node/NodeHttpStack.js new file mode 100644 index 00000000..956a30e6 --- /dev/null +++ b/lib.esm/node/NodeHttpStack.js @@ -0,0 +1,219 @@ +// The url.parse method is superseeded by the url.URL constructor, +// but it is still included in Node.js +import * as http from 'node:http'; +import * as https from 'node:https'; +import { Readable, Transform } from 'node:stream'; +import { parse } from 'node:url'; +import isStream from 'is-stream'; +import throttle from 'lodash.throttle'; +export class NodeHttpStack { + constructor(requestOptions = {}) { + this._requestOptions = requestOptions; + } + createRequest(method, url) { + return new Request(method, url, this._requestOptions); + } + getName() { + return 'NodeHttpStack'; + } +} +class Request { + constructor(method, url, options) { + this._headers = {}; + this._request = null; + this._progressHandler = () => { }; + this._method = method; + this._url = url; + this._requestOptions = options; + } + getMethod() { + return this._method; + } + getURL() { + return this._url; + } + setHeader(header, value) { + this._headers[header] = value; + } + getHeader(header) { + return this._headers[header]; + } + setProgressHandler(progressHandler) { + this._progressHandler = progressHandler; + } + async send(body) { + var _a; + let nodeBody; + if (body != null) { + if (body instanceof Blob) { + nodeBody = new Uint8Array(await body.arrayBuffer()); + } + else if (body instanceof Uint8Array) { + nodeBody = body; + } + else if (ArrayBuffer.isView(body)) { + // Any typed array other than Uint8Array or a DataVew + nodeBody = new Uint8Array(body.buffer, body.byteOffset, body.byteLength); + } + else if (isStream.readable(body)) { + nodeBody = body; + } + else { + throw new Error( + // @ts-expect-error According to the types, this case cannot happen. But + // we still want to try logging the constructor if this code is reached by accident. + `Unsupported HTTP request body type in Node.js HTTP stack: ${typeof body} (constructor: ${(_a = body === null || body === void 0 ? void 0 : body.constructor) === null || _a === void 0 ? void 0 : _a.name})`); + } + } + return new Promise((resolve, reject) => { + const options = { + ...parse(this._url), + ...this._requestOptions, + method: this._method, + headers: { + ...(this._requestOptions.headers || {}), + ...this._headers, + }, + }; + // TODO: What to do here? + // @ts-expect-error We still have to type `size` for `body` + if (body === null || body === void 0 ? void 0 : body.size) { + // @ts-expect-error We still have to type `size` for `body` + options.headers['Content-Length'] = body.size; + } + const httpModule = options.protocol === 'https:' ? https : http; + this._request = httpModule.request(options); + const req = this._request; + req.on('response', (res) => { + const resChunks = []; + res.on('data', (data) => { + resChunks.push(data); + }); + res.on('end', () => { + const responseText = Buffer.concat(resChunks).toString('utf8'); + resolve(new Response(res, responseText)); + }); + }); + req.on('error', (err) => { + reject(err); + }); + if (nodeBody instanceof Readable) { + // Readable stream are piped through a PassThrough instance, which + // counts the number of bytes passed through. This is used, for example, + // when an fs.ReadStream is provided to tus-js-client. + nodeBody.pipe(new ProgressEmitter(this._progressHandler)).pipe(req); + } + else if (nodeBody instanceof Uint8Array) { + // For Buffers and Uint8Arrays (in Node.js all buffers are instances of Uint8Array), + // we write chunks of the buffer to the stream and use that to track the progress. + // This is used when either a Buffer or a normal readable stream is provided + // to tus-js-client. + writeBufferToStreamWithProgress(req, nodeBody, this._progressHandler); + } + else { + req.end(); + } + }); + } + abort() { + if (this._request != null) + this._request.abort(); + return Promise.resolve(); + } + getUnderlyingObject() { + return this._request; + } +} +class Response { + constructor(res, body) { + this._response = res; + this._body = body; + } + getStatus() { + if (this._response.statusCode === undefined) { + throw new Error('no status code available yet'); + } + return this._response.statusCode; + } + getHeader(header) { + const values = this._response.headers[header.toLowerCase()]; + if (Array.isArray(values)) { + return values.join(', '); + } + return values; + } + getBody() { + return this._body; + } + getUnderlyingObject() { + return this._response; + } +} +// ProgressEmitter is a simple PassThrough-style transform stream which keeps +// track of the number of bytes which have been piped through it and will +// invoke the `onprogress` function whenever new number are available. +class ProgressEmitter extends Transform { + constructor(onprogress) { + super(); + this._position = 0; + // The _onprogress property will be invoked, whenever a chunk is piped + // through this transformer. Since chunks are usually quite small (64kb), + // these calls can occur frequently, especially when you have a good + // connection to the remote server. Therefore, we are throtteling them to + // prevent accessive function calls. + this._onprogress = throttle(onprogress, 100, { + leading: true, + trailing: false, + }); + } + _transform(chunk, _encoding, callback) { + this._position += chunk.length; + this._onprogress(this._position); + callback(null, chunk); + } +} +// writeBufferToStreamWithProgress writes chunks from `source` (either a +// Buffer or Uint8Array) to the readable stream `stream`. +// The size of the chunk depends on the stream's highWaterMark to fill the +// stream's internal buffer as best as possible. +// If the internal buffer is full, the callback `onprogress` will be invoked +// to notify about the write progress. Writing will be resumed once the internal +// buffer is empty, as indicated by the emitted `drain` event. +// See https://nodejs.org/docs/latest/api/stream.html#buffering for more details +// on the buffering behavior of streams. +function writeBufferToStreamWithProgress(stream, source, onprogress) { + onprogress = throttle(onprogress, 100, { + leading: true, + trailing: false, + }); + let offset = 0; + function writeNextChunk() { + // Take at most the amount of bytes from highWaterMark. This should fill the streams + // internal buffer already. + const chunkSize = Math.min(stream.writableHighWaterMark, source.length - offset); + // Note: We use subarray instead of slice because it works without copying data for + // Buffers and Uint8Arrays. + const chunk = source.subarray(offset, offset + chunkSize); + offset += chunk.length; + // `write` returns true if the internal buffer is not full and we should write more. + // If the stream is destroyed because the request is aborted, it will return false + // and no 'drain' event is emitted, so won't continue writing data. + const canContinue = stream.write(chunk); + if (!canContinue) { + // If the buffer is full, wait for the 'drain' event to write more data. + stream.once('drain', writeNextChunk); + onprogress(offset); + } + else if (offset < source.length) { + // If there's still data to write and the buffer is not full, write next chunk. + writeNextChunk(); + } + else { + // If all data has been written, close the stream if needed, and emit a 'finish' event. + stream.end(); + } + } + // Start writing the first chunk. + writeNextChunk(); +} +//# sourceMappingURL=NodeHttpStack.js.map \ No newline at end of file diff --git a/lib.esm/node/NodeHttpStack.js.map b/lib.esm/node/NodeHttpStack.js.map new file mode 100644 index 00000000..f521c94f --- /dev/null +++ b/lib.esm/node/NodeHttpStack.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NodeHttpStack.js","sourceRoot":"","sources":["../../lib/node/NodeHttpStack.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.esm/node/fileSignature.d.ts b/lib.esm/node/fileSignature.d.ts new file mode 100644 index 00000000..3019c80b --- /dev/null +++ b/lib.esm/node/fileSignature.d.ts @@ -0,0 +1,2 @@ +import type { UploadInput, UploadOptions } from '../options.js'; +export declare function fingerprint(file: UploadInput, options: UploadOptions): Promise; diff --git a/lib.esm/node/fileSignature.js b/lib.esm/node/fileSignature.js new file mode 100644 index 00000000..7df22f8e --- /dev/null +++ b/lib.esm/node/fileSignature.js @@ -0,0 +1,23 @@ +import { createHash } from 'node:crypto'; +import { ReadStream } from 'node:fs'; +import { stat } from 'node:fs/promises'; +import * as path from 'node:path'; +export async function fingerprint(file, options) { + if (Buffer.isBuffer(file)) { + // create MD5 hash for buffer type + const blockSize = 64 * 1024; // 64kb + const content = file.slice(0, Math.min(blockSize, file.length)); + const hash = createHash('md5').update(content).digest('hex'); + const ret = ['node-buffer', hash, file.length, options.endpoint].join('-'); + return ret; + } + if (file instanceof ReadStream && file.path != null) { + const name = path.resolve(Buffer.isBuffer(file.path) ? file.path.toString('utf-8') : file.path); + const info = await stat(file.path); + const ret = ['node-file', name, info.size, info.mtime.getTime(), options.endpoint].join('-'); + return ret; + } + // fingerprint cannot be computed for file input type + return null; +} +//# sourceMappingURL=fileSignature.js.map \ No newline at end of file diff --git a/lib.esm/node/fileSignature.js.map b/lib.esm/node/fileSignature.js.map new file mode 100644 index 00000000..c8f19c4b --- /dev/null +++ b/lib.esm/node/fileSignature.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fileSignature.js","sourceRoot":"","sources":["../../lib/node/fileSignature.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAGjC,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAiB,EACjB,OAAsB;IAEtB,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,kCAAkC;QAClC,MAAM,SAAS,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,OAAO;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;QAC/D,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC5D,MAAM,GAAG,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC1E,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,IAAI,IAAI,YAAY,UAAU,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/F,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClC,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAE5F,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,qDAAqD;IACrD,OAAO,IAAI,CAAA;AACb,CAAC"} \ No newline at end of file diff --git a/lib.esm/node/index.d.ts b/lib.esm/node/index.d.ts new file mode 100644 index 00000000..df45263c --- /dev/null +++ b/lib.esm/node/index.d.ts @@ -0,0 +1,47 @@ +import { DetailedError } from '../DetailedError.js'; +import { NoopUrlStorage } from '../NoopUrlStorage.js'; +import { enableDebugLog } from '../logger.js'; +import type { UploadInput, UploadOptions } from '../options.js'; +import { BaseUpload } from '../upload.js'; +import { canStoreURLs } from './FileUrlStorage.js'; +import { NodeFileReader } from './NodeFileReader.js'; +import { NodeHttpStack as DefaultHttpStack } from './NodeHttpStack.js'; +import { fingerprint } from './fileSignature.js'; +declare const defaultOptions: { + httpStack: DefaultHttpStack; + fileReader: NodeFileReader; + urlStorage: NoopUrlStorage; + fingerprint: typeof fingerprint; + endpoint: undefined; + uploadUrl: undefined; + metadata: {}; + metadataForPartialUploads: {}; + uploadSize: undefined; + onProgress: undefined; + onChunkComplete: undefined; + onSuccess: undefined; + onError: undefined; + onUploadUrlAvailable: undefined; + overridePatchMethod: boolean; + headers: {}; + addRequestId: boolean; + onBeforeRequest: undefined; + onAfterResponse: undefined; + onShouldRetry: (err: DetailedError) => boolean; + chunkSize: number; + retryDelays: number[]; + parallelUploads: number; + parallelUploadBoundaries: undefined; + storeFingerprintForResuming: boolean; + removeFingerprintOnSuccess: boolean; + uploadLengthDeferred: boolean; + uploadDataDuringCreation: boolean; + protocol: UploadOptions["protocol"]; +}; +declare class Upload extends BaseUpload { + constructor(file: UploadInput, options?: Partial); + static terminate(url: string, options?: Partial): Promise; +} +declare const isSupported = true; +export { Upload, defaultOptions, isSupported, canStoreURLs, enableDebugLog, DetailedError }; +export type * from '../options.js'; diff --git a/lib.esm/node/index.js b/lib.esm/node/index.js new file mode 100644 index 00000000..c4dcf3ee --- /dev/null +++ b/lib.esm/node/index.js @@ -0,0 +1,32 @@ +import { DetailedError } from '../DetailedError.js'; +import { NoopUrlStorage } from '../NoopUrlStorage.js'; +import { enableDebugLog } from '../logger.js'; +import { BaseUpload, defaultOptions as baseDefaultOptions, terminate } from '../upload.js'; +import { canStoreURLs } from './FileUrlStorage.js'; +import { NodeFileReader } from './NodeFileReader.js'; +import { NodeHttpStack as DefaultHttpStack } from './NodeHttpStack.js'; +import { fingerprint } from './fileSignature.js'; +const defaultOptions = { + ...baseDefaultOptions, + httpStack: new DefaultHttpStack(), + fileReader: new NodeFileReader(), + urlStorage: new NoopUrlStorage(), + fingerprint, +}; +class Upload extends BaseUpload { + constructor(file, options = {}) { + const allOpts = { ...defaultOptions, ...options }; + super(file, allOpts); + } + static terminate(url, options = {}) { + const allOpts = { ...defaultOptions, ...options }; + return terminate(url, allOpts); + } +} +// The Node.js environment does not have restrictions which may cause +// tus-js-client not to function. +const isSupported = true; +// Note: The exported interface must be the same as in lib/browser/index.ts. +// Any changes should be reflected in both files. +export { Upload, defaultOptions, isSupported, canStoreURLs, enableDebugLog, DetailedError }; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib.esm/node/index.js.map b/lib.esm/node/index.js.map new file mode 100644 index 00000000..7bea6ee6 --- /dev/null +++ b/lib.esm/node/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,UAAU,EAAE,cAAc,IAAI,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE1F,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,MAAM,cAAc,GAAG;IACrB,GAAG,kBAAkB;IACrB,SAAS,EAAE,IAAI,gBAAgB,EAAE;IACjC,UAAU,EAAE,IAAI,cAAc,EAAE;IAChC,UAAU,EAAE,IAAI,cAAc,EAAE;IAChC,WAAW;CACZ,CAAA;AAED,MAAM,MAAO,SAAQ,UAAU;IAC7B,YAAY,IAAiB,EAAE,UAAkC,EAAE;QACjE,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAA;QACjD,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACtB,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,GAAW,EAAE,UAAkC,EAAE;QAChE,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,CAAA;QACjD,OAAO,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChC,CAAC;CACF;AAED,qEAAqE;AACrE,iCAAiC;AACjC,MAAM,WAAW,GAAG,IAAI,CAAA;AAExB,4EAA4E;AAC5E,iDAAiD;AACjD,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA"} \ No newline at end of file diff --git a/lib.esm/node/sources/NodeStreamFileSource.d.ts b/lib.esm/node/sources/NodeStreamFileSource.d.ts new file mode 100644 index 00000000..639aa110 --- /dev/null +++ b/lib.esm/node/sources/NodeStreamFileSource.d.ts @@ -0,0 +1,29 @@ +import type { Readable } from 'node:stream'; +import type { FileSource } from '../../options.js'; +/** + * StreamSource provides an interface to obtain slices of a Readable stream for + * various ranges. + * It will buffer read data, to allow for following pattern: + * - Call slice(startA, endA) will buffer the data of the requested range + * - Call slice(startB, endB) will return data from the buffer if startA <= startB <= endA. + * If endB > endA, it will also consume new data from the stream. + * Note that it is forbidden to call with startB < startA or startB > endA. In other words, + * the slice calls cannot seek back and must not skip data from the stream. + */ +export declare class NodeStreamFileSource implements FileSource { + size: null; + private _stream; + private _buf; + private _bufPos; + private _ended; + private _error; + constructor(stream: Readable); + slice(start: number, end: number): Promise<{ + value: Buffer & { + size?: number; + }; + size: number; + done: boolean; + }>; + close(): void; +} diff --git a/lib.esm/node/sources/NodeStreamFileSource.js b/lib.esm/node/sources/NodeStreamFileSource.js new file mode 100644 index 00000000..0e82b228 --- /dev/null +++ b/lib.esm/node/sources/NodeStreamFileSource.js @@ -0,0 +1,116 @@ +/** + * readChunk reads a chunk with the given size from the given + * stream. It will wait until enough data is available to satisfy + * the size requirement before resolving. + * Only if the stream ends, the function may resolve with a buffer + * smaller than the size argument. + * Note that we rely on the stream behaving as Node.js documents: + * https://nodejs.org/api/stream.html#readablereadsize + */ +async function readChunk(stream, size) { + return new Promise((resolve, reject) => { + const onError = (err) => { + cleanup(); + reject(err); + }; + const onReadable = () => { + // TODO: Node requires size to be less than 1GB. Add a validation for that + const chunk = stream.read(size); + if (chunk != null) { + cleanup(); + resolve(chunk); + } + }; + const onEnd = () => { + cleanup(); + resolve(Buffer.alloc(0)); + }; + const cleanup = () => { + stream.off('error', onError); + stream.off('readable', onReadable); + stream.off('end', onEnd); + }; + stream.once('error', onError); + stream.on('readable', onReadable); + stream.once('end', onEnd); + }); +} +/** + * StreamSource provides an interface to obtain slices of a Readable stream for + * various ranges. + * It will buffer read data, to allow for following pattern: + * - Call slice(startA, endA) will buffer the data of the requested range + * - Call slice(startB, endB) will return data from the buffer if startA <= startB <= endA. + * If endB > endA, it will also consume new data from the stream. + * Note that it is forbidden to call with startB < startA or startB > endA. In other words, + * the slice calls cannot seek back and must not skip data from the stream. + */ +// TODO: Consider converting the node stream in a web stream. Then we can share the stream +// handling between browsers and node.js. +export class NodeStreamFileSource { + constructor(stream) { + // Setting the size to null indicates that we have no calculation available + // for how much data this stream will emit requiring the user to specify + // it manually (see the `uploadSize` option). + this.size = null; + this._buf = Buffer.alloc(0); + this._bufPos = 0; + this._ended = false; + this._error = null; + this._stream = stream; + stream.pause(); + stream.on('end', () => { + this._ended = true; + }); + stream.on('error', (err) => { + this._error = err; + }); + } + async slice(start, end) { + // Fail fast if the caller requests a proportion of the data which is not + // available any more. + if (start < this._bufPos) { + throw new Error('cannot slice from position which we already seeked away'); + } + if (start > this._bufPos + this._buf.length) { + throw new Error('slice start is outside of buffer (currently not implemented)'); + } + if (this._error) { + throw this._error; + } + let returnBuffer; + // Always attempt to drain the buffer first, even if this means that we + // return less data than the caller requested. + if (start < this._bufPos + this._buf.length) { + const bufStart = start - this._bufPos; + const bufEnd = Math.min(this._buf.length, end - this._bufPos); + returnBuffer = this._buf.slice(bufStart, bufEnd); + } + else { + returnBuffer = Buffer.alloc(0); + } + // If the stream has ended already, read calls would not finish, so return early here. + if (this._ended) { + const size = returnBuffer.length; + return { value: returnBuffer, size, done: true }; + } + // If we could not satisfy the slice request from the buffer only, read more data from + // the stream and add it to the buffer. + const requestedSize = end - start; + if (requestedSize > returnBuffer.length) { + // Note: We assume that the stream returns not more than the requested size. + const newChunk = await readChunk(this._stream, requestedSize - returnBuffer.length); + // Append the new chunk to the buffer + returnBuffer = Buffer.concat([returnBuffer, newChunk]); + } + // Important: Store the read data, so consecutive slice calls can access the same data. + this._buf = returnBuffer; + this._bufPos = start; + const size = returnBuffer.length; + return { value: returnBuffer, size, done: this._ended }; + } + close() { + this._stream.destroy(); + } +} +//# sourceMappingURL=NodeStreamFileSource.js.map \ No newline at end of file diff --git a/lib.esm/node/sources/NodeStreamFileSource.js.map b/lib.esm/node/sources/NodeStreamFileSource.js.map new file mode 100644 index 00000000..6d285f3b --- /dev/null +++ b/lib.esm/node/sources/NodeStreamFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NodeStreamFileSource.js","sourceRoot":"","sources":["../../../lib/node/sources/NodeStreamFileSource.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.esm/node/sources/PathFileSource.d.ts b/lib.esm/node/sources/PathFileSource.d.ts new file mode 100644 index 00000000..cf22ec4a --- /dev/null +++ b/lib.esm/node/sources/PathFileSource.d.ts @@ -0,0 +1,17 @@ +import { type ReadStream } from 'node:fs'; +import type { FileSource, PathReference } from '../../options.js'; +export declare function getFileSourceFromPath(file: PathReference): Promise; +export declare class PathFileSource implements FileSource { + size: number; + private _file; + private _path; + constructor(file: PathReference, path: string, size: number); + slice(start: number, end: number): Promise<{ + value: ReadStream & { + size?: number; + }; + size: number; + done: boolean; + }>; + close(): void; +} diff --git a/lib.esm/node/sources/PathFileSource.js b/lib.esm/node/sources/PathFileSource.js new file mode 100644 index 00000000..9a142407 --- /dev/null +++ b/lib.esm/node/sources/PathFileSource.js @@ -0,0 +1,56 @@ +import { createReadStream, promises as fsPromises } from 'node:fs'; +export async function getFileSourceFromPath(file) { + var _a; + const path = file.path.toString(); + const { size } = await fsPromises.stat(path); + // The path reference might be configured to not read from the beginning + // to the end, but instead from a slice in between. In this case, we consider + // that range to indicate the actual uploadable size. + // This happens, for example, if a path reference is used with the `parallelUploads` + // option. There, the path reference is sliced into multiple fs.ReadStreams to fit the + // number of `parallelUploads`. Each ReadStream has `start` and `end` set. + // Note: `stream.end` is Infinity by default, so we need the check `isFinite`. + // Note: `stream.end` is treated inclusively, so we need to add 1 here. + // See the comment in slice() for more context. + const start = (_a = file.start) !== null && _a !== void 0 ? _a : 0; + const end = file.end != null && Number.isFinite(file.end) ? file.end + 1 : size; + const actualSize = end - start; + return new PathFileSource(file, path, actualSize); +} +export class PathFileSource { + constructor(file, path, size) { + this._file = file; + this._path = path; + this.size = size; + } + slice(start, end) { + var _a; + // TODO: Does this create multiple file descriptors? Can we reduce this by + // using a file handle instead? + // The path reference might be configured to not read from the beginning, + // but instead start at a different offset. The start value from the caller + // does not include the offset, so we need to add this offset to our range later. + // This happens, for example, if a path reference is used with the `parallelUploads` + // option. First, the path reference is sliced into multiple fs.ReadStreams to fit the + // number of `parallelUploads`. Each ReadStream has `start` set. + const offset = (_a = this._file.start) !== null && _a !== void 0 ? _a : 0; + const stream = createReadStream(this._path, { + start: offset + start, + // The `end` option for createReadStream is treated inclusively + // (see https://nodejs.org/api/fs.html#fs_fs_createreadstream_path_options). + // However, the Buffer#slice(start, end) and also our Source#slice(start, end) + // method treat the end range exclusively, so we have to subtract 1. + // This prevents an off-by-one error when reporting upload progress. + end: offset + end - 1, + autoClose: true, + }); + const size = Math.min(end - start, this.size); + const done = size >= this.size; + return Promise.resolve({ value: stream, size, done }); + } + close() { + // TODO: Ensure that the read streams are closed + // TODO: Previously, the passed fs.ReadStream was closed here. Should we keep this behavior? If not, this is a breaking change. + } +} +//# sourceMappingURL=PathFileSource.js.map \ No newline at end of file diff --git a/lib.esm/node/sources/PathFileSource.js.map b/lib.esm/node/sources/PathFileSource.js.map new file mode 100644 index 00000000..e91da628 --- /dev/null +++ b/lib.esm/node/sources/PathFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"PathFileSource.js","sourceRoot":"","sources":["../../../lib/node/sources/PathFileSource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,gBAAgB,EAAE,QAAQ,IAAI,UAAU,EAAE,MAAM,SAAS,CAAA;AAGnF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAmB;;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAE5C,wEAAwE;IACxE,6EAA6E;IAC7E,qDAAqD;IACrD,oFAAoF;IACpF,sFAAsF;IACtF,0EAA0E;IAC1E,8EAA8E;IAC9E,uEAAuE;IACvE,+CAA+C;IAC/C,MAAM,KAAK,GAAG,MAAA,IAAI,CAAC,KAAK,mCAAI,CAAC,CAAA;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC/E,MAAM,UAAU,GAAG,GAAG,GAAG,KAAK,CAAA;IAE9B,OAAO,IAAI,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAA;AACnD,CAAC;AAED,MAAM,OAAO,cAAc;IAOzB,YAAY,IAAmB,EAAE,IAAY,EAAE,IAAY;QACzD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAED,KAAK,CAAC,KAAa,EAAE,GAAW;;QAC9B,0EAA0E;QAC1E,+BAA+B;QAC/B,yEAAyE;QACzE,2EAA2E;QAC3E,iFAAiF;QACjF,oFAAoF;QACpF,sFAAsF;QACtF,gEAAgE;QAChE,MAAM,MAAM,GAAG,MAAA,IAAI,CAAC,KAAK,CAAC,KAAK,mCAAI,CAAC,CAAA;QAEpC,MAAM,MAAM,GAAmC,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE;YAC1E,KAAK,EAAE,MAAM,GAAG,KAAK;YACrB,+DAA+D;YAC/D,4EAA4E;YAC5E,8EAA8E;YAC9E,oEAAoE;YACpE,oEAAoE;YACpE,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,CAAC;YACrB,SAAS,EAAE,IAAI;SAChB,CAAC,CAAA;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAC7C,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAA;QAC9B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IACvD,CAAC;IAED,KAAK;QACH,gDAAgD;QAChD,+HAA+H;IACjI,CAAC;CACF"} \ No newline at end of file diff --git a/lib.esm/options.d.ts b/lib.esm/options.d.ts new file mode 100644 index 00000000..c6801722 --- /dev/null +++ b/lib.esm/options.d.ts @@ -0,0 +1,130 @@ +import type { Readable as NodeReadableStream } from 'node:stream'; +import type { DetailedError } from './DetailedError.js'; +export declare const PROTOCOL_TUS_V1 = "tus-v1"; +export declare const PROTOCOL_IETF_DRAFT_03 = "ietf-draft-03"; +export declare const PROTOCOL_IETF_DRAFT_05 = "ietf-draft-05"; +/** + * ReactNativeFile describes the structure that is returned from the + * Expo image picker (see https://docs.expo.dev/versions/latest/sdk/imagepicker/) + * TODO: Should these properties be fileName and fileSize instead? + * TODO: What about other file pickers without Expo? + * TODO: Should this be renamed to Expo? + * TODO: Only size is relevant for us. Not the rest. + */ +export interface ReactNativeFile { + uri: string; + name?: string; + size?: string; + exif?: Record; +} +/** + * PathReference is a reference to a file on disk. Currently, it's only supported + * in Node.js. It can be supplied as a normal object or as an instance of `fs.ReadStream`, + * which also statisfies this interface. + * + * Optionally, a start and/or end position can be defined to define a range of bytes from + * the file that should be uploaded instead of the entire file. Both start and end are + * inclusive and start counting at 0, similar to the options accepted by `fs.createReadStream`. + */ +export interface PathReference { + path: string | Buffer; + start?: number; + end?: number; +} +export type UploadInput = Blob | ArrayBuffer | SharedArrayBuffer | ArrayBufferView | ReadableStream | NodeReadableStream | PathReference | ReactNativeFile; +export interface UploadOptions { + endpoint?: string; + uploadUrl?: string; + metadata: { + [key: string]: string; + }; + metadataForPartialUploads: UploadOptions['metadata']; + fingerprint: (file: UploadInput, options: UploadOptions) => Promise; + uploadSize?: number; + onProgress?: (bytesSent: number, bytesTotal: number | null) => void; + onChunkComplete?: (chunkSize: number, bytesAccepted: number, bytesTotal: number | null) => void; + onSuccess?: (payload: OnSuccessPayload) => void; + onError?: (error: Error | DetailedError) => void; + onShouldRetry?: (error: DetailedError, retryAttempt: number, options: UploadOptions) => boolean; + onUploadUrlAvailable?: () => void | Promise; + overridePatchMethod: boolean; + headers: { + [key: string]: string; + }; + addRequestId: boolean; + onBeforeRequest?: (req: HttpRequest) => void | Promise; + onAfterResponse?: (req: HttpRequest, res: HttpResponse) => void | Promise; + chunkSize: number; + retryDelays: number[]; + parallelUploads: number; + parallelUploadBoundaries?: { + start: number; + end: number; + }[]; + storeFingerprintForResuming: boolean; + removeFingerprintOnSuccess: boolean; + uploadLengthDeferred: boolean; + uploadDataDuringCreation: boolean; + urlStorage: UrlStorage; + fileReader: FileReader; + httpStack: HttpStack; + protocol: typeof PROTOCOL_TUS_V1 | typeof PROTOCOL_IETF_DRAFT_03 | typeof PROTOCOL_IETF_DRAFT_05; +} +export interface OnSuccessPayload { + lastResponse: HttpResponse; +} +export interface UrlStorage { + findAllUploads(): Promise; + findUploadsByFingerprint(fingerprint: string): Promise; + removeUpload(urlStorageKey: string): Promise; + addUpload(fingerprint: string, upload: PreviousUpload): Promise; +} +export interface PreviousUpload { + size: number | null; + metadata: { + [key: string]: string; + }; + creationTime: string; + uploadUrl?: string; + parallelUploadUrls?: string[]; + urlStorageKey: string; +} +export interface FileReader { + openFile(input: UploadInput, chunkSize: number): Promise; +} +export interface FileSource { + size: number | null; + slice(start: number, end: number): Promise; + close(): void; +} +export type SliceType = Blob | ArrayBufferView | NodeReadableStream; +export type SliceResult = { + done: true; + value: null; + size: null; +} | { + done: boolean; + value: NonNullable; + size: number; +}; +export interface HttpStack { + createRequest(method: string, url: string): HttpRequest; + getName(): string; +} +export type HttpProgressHandler = (bytesSent: number) => void; +export interface HttpRequest { + getMethod(): string; + getURL(): string; + setHeader(header: string, value: string): void; + getHeader(header: string): string | undefined; + setProgressHandler(handler: HttpProgressHandler): void; + send(body?: SliceType): Promise; + abort(): Promise; + getUnderlyingObject(): unknown; +} +export interface HttpResponse { + getStatus(): number; + getHeader(header: string): string | undefined; + getBody(): string; + getUnderlyingObject(): unknown; +} diff --git a/lib.esm/options.js b/lib.esm/options.js new file mode 100644 index 00000000..64c024dd --- /dev/null +++ b/lib.esm/options.js @@ -0,0 +1,4 @@ +export const PROTOCOL_TUS_V1 = 'tus-v1'; +export const PROTOCOL_IETF_DRAFT_03 = 'ietf-draft-03'; +export const PROTOCOL_IETF_DRAFT_05 = 'ietf-draft-05'; +//# sourceMappingURL=options.js.map \ No newline at end of file diff --git a/lib.esm/options.js.map b/lib.esm/options.js.map new file mode 100644 index 00000000..8a280786 --- /dev/null +++ b/lib.esm/options.js.map @@ -0,0 +1 @@ +{"version":3,"file":"options.js","sourceRoot":"","sources":["../lib/options.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAA;AACvC,MAAM,CAAC,MAAM,sBAAsB,GAAG,eAAe,CAAA;AACrD,MAAM,CAAC,MAAM,sBAAsB,GAAG,eAAe,CAAA"} \ No newline at end of file diff --git a/lib.esm/package.json b/lib.esm/package.json new file mode 100644 index 00000000..089153bc --- /dev/null +++ b/lib.esm/package.json @@ -0,0 +1 @@ +{"type":"module"} diff --git a/lib.esm/reactnative/isReactNative.d.ts b/lib.esm/reactnative/isReactNative.d.ts new file mode 100644 index 00000000..4f0ff67d --- /dev/null +++ b/lib.esm/reactnative/isReactNative.d.ts @@ -0,0 +1,3 @@ +import type { ReactNativeFile } from '../options.js'; +export declare function isReactNativePlatform(): boolean; +export declare function isReactNativeFile(input: unknown): input is ReactNativeFile; diff --git a/lib.esm/reactnative/isReactNative.js b/lib.esm/reactnative/isReactNative.js new file mode 100644 index 00000000..037ba770 --- /dev/null +++ b/lib.esm/reactnative/isReactNative.js @@ -0,0 +1,9 @@ +export function isReactNativePlatform() { + return (typeof navigator !== 'undefined' && + typeof navigator.product === 'string' && + navigator.product.toLowerCase() === 'reactnative'); +} +export function isReactNativeFile(input) { + return (input != null && typeof input === 'object' && 'uri' in input && typeof input.uri === 'string'); +} +//# sourceMappingURL=isReactNative.js.map \ No newline at end of file diff --git a/lib.esm/reactnative/isReactNative.js.map b/lib.esm/reactnative/isReactNative.js.map new file mode 100644 index 00000000..4b52f8b6 --- /dev/null +++ b/lib.esm/reactnative/isReactNative.js.map @@ -0,0 +1 @@ +{"version":3,"file":"isReactNative.js","sourceRoot":"","sources":["../../lib/reactnative/isReactNative.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB;IACnC,OAAO,CACL,OAAO,SAAS,KAAK,WAAW;QAChC,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;QACrC,SAAS,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,aAAa,CAClD,CAAA;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,CACL,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAC9F,CAAA;AACH,CAAC"} \ No newline at end of file diff --git a/lib.esm/reactnative/uriToBlob.d.ts b/lib.esm/reactnative/uriToBlob.d.ts new file mode 100644 index 00000000..9f35ddf6 --- /dev/null +++ b/lib.esm/reactnative/uriToBlob.d.ts @@ -0,0 +1,6 @@ +/** + * 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 declare function uriToBlob(uri: string): Promise; diff --git a/lib.esm/reactnative/uriToBlob.js b/lib.esm/reactnative/uriToBlob.js new file mode 100644 index 00000000..545a6a17 --- /dev/null +++ b/lib.esm/reactnative/uriToBlob.js @@ -0,0 +1,21 @@ +/** + * 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 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(); + }); +} +//# sourceMappingURL=uriToBlob.js.map \ No newline at end of file diff --git a/lib.esm/reactnative/uriToBlob.js.map b/lib.esm/reactnative/uriToBlob.js.map new file mode 100644 index 00000000..5774a683 --- /dev/null +++ b/lib.esm/reactnative/uriToBlob.js.map @@ -0,0 +1 @@ +{"version":3,"file":"uriToBlob.js","sourceRoot":"","sources":["../../lib/reactnative/uriToBlob.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,cAAc,EAAE,CAAA;QAChC,GAAG,CAAC,YAAY,GAAG,MAAM,CAAA;QACzB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE;YAChB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAA;YACzB,OAAO,CAAC,IAAI,CAAC,CAAA;QACf,CAAC,CAAA;QACD,GAAG,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,EAAE;YACpB,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC,CAAA;QACD,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QACpB,GAAG,CAAC,IAAI,EAAE,CAAA;IACZ,CAAC,CAAC,CAAA;AACJ,CAAC"} \ No newline at end of file diff --git a/lib.esm/sources/ArrayBufferViewFileSource.d.ts b/lib.esm/sources/ArrayBufferViewFileSource.d.ts new file mode 100644 index 00000000..e540a35c --- /dev/null +++ b/lib.esm/sources/ArrayBufferViewFileSource.d.ts @@ -0,0 +1,15 @@ +import type { FileSource, SliceResult } from '../options.js'; +/** + * ArrayBufferViewFileSource implements FileSource for ArrayBufferView instances + * (e.g. TypedArry or DataView). + * + * Note that the underlying ArrayBuffer should not change once passed to tus-js-client + * or it will lead to weird behavior. + */ +export declare class ArrayBufferViewFileSource implements FileSource { + private _view; + size: number; + constructor(view: ArrayBufferView); + slice(start: number, end: number): Promise; + close(): void; +} diff --git a/lib.esm/sources/ArrayBufferViewFileSource.js b/lib.esm/sources/ArrayBufferViewFileSource.js new file mode 100644 index 00000000..3b8444a1 --- /dev/null +++ b/lib.esm/sources/ArrayBufferViewFileSource.js @@ -0,0 +1,28 @@ +/** + * ArrayBufferViewFileSource implements FileSource for ArrayBufferView instances + * (e.g. TypedArry or DataView). + * + * Note that the underlying ArrayBuffer should not change once passed to tus-js-client + * or it will lead to weird behavior. + */ +export class ArrayBufferViewFileSource { + constructor(view) { + this._view = view; + this.size = view.byteLength; + } + slice(start, end) { + const buffer = this._view.buffer; + const startInBuffer = this._view.byteOffset + start; + end = Math.min(end, this.size); // ensure end is finite and not greater than size + const byteLength = end - start; + // Use DataView instead of ArrayBuffer.slice to avoid copying the buffer. + const value = new DataView(buffer, startInBuffer, byteLength); + const size = value.byteLength; + const done = end >= this.size; + return Promise.resolve({ value, size, done }); + } + close() { + // Nothing to do here since we don't need to release any resources. + } +} +//# sourceMappingURL=ArrayBufferViewFileSource.js.map \ No newline at end of file diff --git a/lib.esm/sources/ArrayBufferViewFileSource.js.map b/lib.esm/sources/ArrayBufferViewFileSource.js.map new file mode 100644 index 00000000..403deed4 --- /dev/null +++ b/lib.esm/sources/ArrayBufferViewFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ArrayBufferViewFileSource.js","sourceRoot":"","sources":["../../lib/sources/ArrayBufferViewFileSource.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,yBAAyB;IAKpC,YAAY,IAAqB;QAC/B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,UAAU,CAAA;IAC7B,CAAC;IAED,KAAK,CAAC,KAAa,EAAE,GAAW;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;QAChC,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAA;QACnD,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA,CAAC,iDAAiD;QAChF,MAAM,UAAU,GAAG,GAAG,GAAG,KAAK,CAAA;QAE9B,yEAAyE;QACzE,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,CAAC,CAAA;QAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAA;QAC7B,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAA;QAE7B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/C,CAAC;IAED,KAAK;QACH,mEAAmE;IACrE,CAAC;CACF"} \ No newline at end of file diff --git a/lib.esm/sources/BlobFileSource.d.ts b/lib.esm/sources/BlobFileSource.d.ts new file mode 100644 index 00000000..fb48b7da --- /dev/null +++ b/lib.esm/sources/BlobFileSource.d.ts @@ -0,0 +1,11 @@ +import type { FileSource, SliceResult } from '../options.js'; +/** + * BlobFileSource implements FileSource for Blobs (and therefore also for File instances). + */ +export declare class BlobFileSource implements FileSource { + private _file; + size: number; + constructor(file: Blob); + slice(start: number, end: number): Promise; + close(): void; +} diff --git a/lib.esm/sources/BlobFileSource.js b/lib.esm/sources/BlobFileSource.js new file mode 100644 index 00000000..c6ddd8b8 --- /dev/null +++ b/lib.esm/sources/BlobFileSource.js @@ -0,0 +1,32 @@ +import { isCordova } from '../cordova/isCordova.js'; +import { readAsByteArray } from '../cordova/readAsByteArray.js'; +/** + * BlobFileSource implements FileSource for Blobs (and therefore also for File instances). + */ +export class BlobFileSource { + constructor(file) { + this._file = file; + this.size = file.size; + } + async slice(start, end) { + // TODO: This looks fishy. We should test how this actually works in Cordova + // and consider moving this into the lib/cordova/ directory. + // 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()) { + const value = await readAsByteArray(this._file.slice(start, end)); + const size = value.length; + const done = end >= this.size; + return { value, size, done }; + } + const value = this._file.slice(start, end); + const size = value.size; + const done = end >= this.size; + return { value, size, done }; + } + close() { + // Nothing to do here since we don't need to release any resources. + } +} +//# sourceMappingURL=BlobFileSource.js.map \ No newline at end of file diff --git a/lib.esm/sources/BlobFileSource.js.map b/lib.esm/sources/BlobFileSource.js.map new file mode 100644 index 00000000..543e0429 --- /dev/null +++ b/lib.esm/sources/BlobFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"BlobFileSource.js","sourceRoot":"","sources":["../../lib/sources/BlobFileSource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAG/D;;GAEG;AACH,MAAM,OAAO,cAAc;IAKzB,YAAY,IAAU;QACpB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;IACvB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,GAAW;QACpC,4EAA4E;QAC5E,4DAA4D;QAC5D,gEAAgE;QAChE,4BAA4B;QAC5B,8FAA8F;QAC9F,IAAI,SAAS,EAAE,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAA;YACjE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAA;YACzB,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAA;YAE7B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;QAC9B,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;QACvB,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAA;QAE7B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC9B,CAAC;IAED,KAAK;QACH,mEAAmE;IACrE,CAAC;CACF"} \ No newline at end of file diff --git a/lib.esm/sources/WebStreamFileSource.d.ts b/lib.esm/sources/WebStreamFileSource.d.ts new file mode 100644 index 00000000..80bb2fc5 --- /dev/null +++ b/lib.esm/sources/WebStreamFileSource.d.ts @@ -0,0 +1,16 @@ +import type { FileSource, SliceResult } from '../options.js'; +/** + * WebStreamFileSource implements FileSource for Web Streams. + */ +export declare class WebStreamFileSource implements FileSource { + private _reader; + private _buffer; + private _bufferOffset; + private _done; + size: null; + constructor(stream: ReadableStream); + slice(start: number, end: number): Promise; + private _readUntilEnoughDataOrDone; + private _getDataFromBuffer; + close(): void; +} diff --git a/lib.esm/sources/WebStreamFileSource.js b/lib.esm/sources/WebStreamFileSource.js new file mode 100644 index 00000000..9b0f0267 --- /dev/null +++ b/lib.esm/sources/WebStreamFileSource.js @@ -0,0 +1,107 @@ +function len(blobOrArray) { + if (blobOrArray === undefined) + return 0; + if (blobOrArray instanceof Blob) + 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 instanceof Blob && b instanceof Blob) { + return new Blob([a, b], { type: a.type }); + } + if (a instanceof Uint8Array && b instanceof Uint8Array) { + const c = new Uint8Array(a.length + b.length); + c.set(a); + c.set(b, a.length); + return c; + } + throw new Error('Unknown data type'); +} +/** + * WebStreamFileSource implements FileSource for Web Streams. + */ +// TODO: Can we share code with NodeStreamFileSource? +export class WebStreamFileSource { + constructor(stream) { + // _bufferOffset defines at which position the content of _buffer (if it is set) + // is located in the view of the entire stream. It does not mean at which offset + // the content in _buffer begins. + this._bufferOffset = 0; + this._done = false; + // Setting the size to null indicates that we have no calculation available + // for how much data this stream will emit requiring the user to specify + // it manually (see the `uploadSize` option). + this.size = null; + if (stream.locked) { + throw new Error('Readable stream is already locked to reader. tus-js-client cannot obtain a new reader.'); + } + this._reader = stream.getReader(); + } + async slice(start, end) { + if (start < this._bufferOffset) { + throw new Error("Requested data is before the reader's current offset"); + } + return await this._readUntilEnoughDataOrDone(start, end); + } + async _readUntilEnoughDataOrDone(start, end) { + const hasEnoughData = end <= this._bufferOffset + len(this._buffer); + if (this._done || hasEnoughData) { + const value = this._getDataFromBuffer(start, end); + if (value === null) { + return { value: null, size: null, done: true }; + } + const size = value instanceof Blob ? value.size : value.length; + const done = this._done; + return { value, size, done }; + } + const { value, done } = await this._reader.read(); + if (done) { + this._done = true; + } + else { + const chunkSize = len(value); + // If all of the chunk occurs before 'start' then drop it and clear the buffer. + // This greatly improves performance when reading from a stream we haven't started processing yet and 'start' is near the end of the file. + // Rather than buffering all of the unused data in memory just to only read a chunk near the end, rather immidiately drop data which will never be read. + if (this._bufferOffset + len(this._buffer) + chunkSize < start) { + this._buffer = undefined; + this._bufferOffset += chunkSize; + } + else if (this._buffer === undefined) { + this._buffer = value; + } + else { + this._buffer = concat(this._buffer, value); + } + } + return await this._readUntilEnoughDataOrDone(start, end); + } + _getDataFromBuffer(start, end) { + if (this._buffer === undefined) { + throw new Error('cannot _getDataFromBuffer because _buffer is unset'); + } + // 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() { + this._reader.cancel(); + } +} +//# sourceMappingURL=WebStreamFileSource.js.map \ No newline at end of file diff --git a/lib.esm/sources/WebStreamFileSource.js.map b/lib.esm/sources/WebStreamFileSource.js.map new file mode 100644 index 00000000..6980b568 --- /dev/null +++ b/lib.esm/sources/WebStreamFileSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WebStreamFileSource.js","sourceRoot":"","sources":["../../lib/sources/WebStreamFileSource.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.esm/upload.d.ts b/lib.esm/upload.d.ts new file mode 100644 index 00000000..f19b1399 --- /dev/null +++ b/lib.esm/upload.d.ts @@ -0,0 +1,189 @@ +import { DetailedError } from './DetailedError.js'; +import { type HttpRequest, type HttpResponse, type PreviousUpload, type SliceType, type UploadInput, type UploadOptions } from './options.js'; +export declare const defaultOptions: { + endpoint: undefined; + uploadUrl: undefined; + metadata: {}; + metadataForPartialUploads: {}; + fingerprint: undefined; + uploadSize: undefined; + onProgress: undefined; + onChunkComplete: undefined; + onSuccess: undefined; + onError: undefined; + onUploadUrlAvailable: undefined; + overridePatchMethod: boolean; + headers: {}; + addRequestId: boolean; + onBeforeRequest: undefined; + onAfterResponse: undefined; + onShouldRetry: typeof defaultOnShouldRetry; + chunkSize: number; + retryDelays: number[]; + parallelUploads: number; + parallelUploadBoundaries: undefined; + storeFingerprintForResuming: boolean; + removeFingerprintOnSuccess: boolean; + uploadLengthDeferred: boolean; + uploadDataDuringCreation: boolean; + urlStorage: undefined; + fileReader: undefined; + httpStack: undefined; + protocol: UploadOptions["protocol"]; +}; +export declare class BaseUpload { + options: UploadOptions; + file: UploadInput; + url: string | null; + private _req?; + private _fingerprint; + private _urlStorageKey?; + private _offset; + private _aborted; + private _size; + private _source?; + private _retryAttempt; + private _retryTimeout?; + private _offsetBeforeRetry; + private _parallelUploads?; + private _parallelUploadUrls?; + private _uploadLengthDeferred; + constructor(file: UploadInput, options: UploadOptions); + findPreviousUploads(): Promise; + resumeFromPreviousUpload(previousUpload: PreviousUpload): void; + start(): void; + private _prepareAndStartUpload; + /** + * Initiate the uploading procedure for a parallelized upload, where one file is split into + * multiple request which are run in parallel. + * + * @api private + */ + private _startParallelUpload; + /** + * Initiate the uploading procedure for a non-parallel upload. Here the entire file is + * uploaded in a sequential matter. + * + * @api private + */ + private _startSingleUpload; + /** + * Abort any running request and stop the current upload. After abort is called, no event + * handler will be invoked anymore. You can use the `start` method to resume the upload + * again. + * If `shouldTerminate` is true, the `terminate` function will be called to remove the + * current upload from the server. + * + * @param {boolean} shouldTerminate True if the upload should be deleted from the server. + * @return {Promise} The Promise will be resolved/rejected when the requests finish. + */ + abort(shouldTerminate?: boolean): Promise; + private _emitError; + private _retryOrEmitError; + /** + * Publishes notification if the upload has been successfully completed. + * + * @param {object} lastResponse Last HTTP response. + * @api private + */ + private _emitSuccess; + /** + * Publishes notification when data has been sent to the server. This + * data may not have been accepted by the server yet. + * + * @param {number} bytesSent Number of bytes sent to the server. + * @param {number|null} bytesTotal Total number of bytes to be sent to the server. + * @api private + */ + private _emitProgress; + /** + * Publishes notification when a chunk of data has been sent to the server + * and accepted by the server. + * @param {number} chunkSize Size of the chunk that was accepted by the server. + * @param {number} bytesAccepted Total number of bytes that have been + * accepted by the server. + * @param {number|null} bytesTotal Total number of bytes to be sent to the server. + * @api private + */ + private _emitChunkComplete; + /** + * Create a new upload using the creation extension by sending a POST + * request to the endpoint. After successful creation the file will be + * uploaded + * + * @api private + */ + private _createUpload; + /** + * Try to resume an existing upload. First a HEAD request will be sent + * to retrieve the offset. If the request fails a new upload will be + * created. In the case of a successful response the file will be uploaded. + * + * @api private + */ + private _resumeUpload; + /** + * Start uploading the file using PATCH requests. The file will be divided + * into chunks as specified in the chunkSize option. During the upload + * the onProgress event handler may be invoked multiple times. + * + * @api private + */ + private _performUpload; + /** + * _addChunktoRequest reads a chunk from the source and sends it using the + * supplied request object. It will not handle the response. + * + * @api private + */ + private _addChunkToRequest; + /** + * _handleUploadResponse is used by requests that haven been sent using _addChunkToRequest + * and already have received a response. + * + * @api private + */ + private _handleUploadResponse; + /** + * Create a new HTTP request object with the given method and URL. + * + * @api private + */ + private _openRequest; + /** + * Remove the entry in the URL storage, if it has been saved before. + * + * @api private + */ + private _removeFromUrlStorage; + /** + * Add the upload URL to the URL storage, if possible. + * + * @api private + */ + private _saveUploadInUrlStorage; + /** + * Send a request with the provided body. + * + * @api private + */ + _sendRequest(req: HttpRequest, body?: SliceType): Promise; +} +/** + * determines if the request should be retried. Will only retry if not a status 4xx except a 409 or 423 + * @param {DetailedError} err + * @returns {boolean} + */ +declare function defaultOnShouldRetry(err: DetailedError): boolean; +/** + * Use the Termination extension to delete an upload from the server by sending a DELETE + * request to the specified upload URL. This is only possible if the server supports the + * Termination extension. If the `options.retryDelays` property is set, the method will + * also retry if an error ocurrs. + * + * @param {String} url The upload's URL which will be terminated. + * @param {object} options Optional options for influencing HTTP requests. + * @return {Promise} The Promise will be resolved/rejected when the requests finish. + */ +export declare function terminate(url: string, options: UploadOptions): Promise; +export {}; diff --git a/lib.esm/upload.js b/lib.esm/upload.js new file mode 100644 index 00000000..a5fa1b77 --- /dev/null +++ b/lib.esm/upload.js @@ -0,0 +1,979 @@ +import { Base64 } from 'js-base64'; +// TODO: Package url-parse is CommonJS. Can we replace this with a ESM package that +// provides WHATWG URL? Then we can get rid of @rollup/plugin-commonjs. +import URL from 'url-parse'; +import { DetailedError } from './DetailedError.js'; +import { log } from './logger.js'; +import { PROTOCOL_IETF_DRAFT_03, PROTOCOL_IETF_DRAFT_05, PROTOCOL_TUS_V1, } from './options.js'; +import { uuid } from './uuid.js'; +export const defaultOptions = { + endpoint: undefined, + uploadUrl: undefined, + metadata: {}, + metadataForPartialUploads: {}, + fingerprint: undefined, + uploadSize: undefined, + onProgress: undefined, + onChunkComplete: undefined, + onSuccess: undefined, + onError: undefined, + onUploadUrlAvailable: undefined, + overridePatchMethod: false, + headers: {}, + addRequestId: false, + onBeforeRequest: undefined, + onAfterResponse: undefined, + onShouldRetry: defaultOnShouldRetry, + chunkSize: Number.POSITIVE_INFINITY, + retryDelays: [0, 1000, 3000, 5000], + parallelUploads: 1, + parallelUploadBoundaries: undefined, + storeFingerprintForResuming: true, + removeFingerprintOnSuccess: false, + uploadLengthDeferred: false, + uploadDataDuringCreation: false, + urlStorage: undefined, + fileReader: undefined, + httpStack: undefined, + protocol: PROTOCOL_TUS_V1, +}; +export class BaseUpload { + constructor(file, options) { + // The URL against which the file will be uploaded + this.url = null; + // The fingerpinrt for the current file (set after start()) + this._fingerprint = null; + // The offset used in the current PATCH request + this._offset = 0; + // True if the current PATCH request has been aborted + this._aborted = false; + // The file's size in bytes + this._size = null; + // The current count of attempts which have been made. Zero indicates none. + this._retryAttempt = 0; + // The offset of the remote upload before the latest attempt was started. + this._offsetBeforeRetry = 0; + // Warn about removed options from previous versions + if ('resume' in options) { + console.log('tus: The `resume` option has been removed in tus-js-client v2. Please use the URL storage API instead.'); + } + // The default options will already be added from the wrapper classes. + this.options = options; + // Cast chunkSize to integer + // TODO: Remove this cast + this.options.chunkSize = Number(this.options.chunkSize); + this._uploadLengthDeferred = this.options.uploadLengthDeferred; + this.file = file; + } + async findPreviousUploads() { + const fingerprint = await this.options.fingerprint(this.file, this.options); + if (!fingerprint) { + throw new Error('tus: unable to calculate fingerprint for this input file'); + } + return await this.options.urlStorage.findUploadsByFingerprint(fingerprint); + } + resumeFromPreviousUpload(previousUpload) { + this.url = previousUpload.uploadUrl || null; + this._parallelUploadUrls = previousUpload.parallelUploadUrls; + this._urlStorageKey = previousUpload.urlStorageKey; + } + start() { + if (!this.file) { + this._emitError(new Error('tus: no file or stream to upload provided')); + return; + } + if (![PROTOCOL_TUS_V1, PROTOCOL_IETF_DRAFT_03, PROTOCOL_IETF_DRAFT_05].includes(this.options.protocol)) { + this._emitError(new Error(`tus: unsupported protocol ${this.options.protocol}`)); + return; + } + if (!this.options.endpoint && !this.options.uploadUrl && !this.url) { + this._emitError(new Error('tus: neither an endpoint or an upload URL is provided')); + return; + } + const { retryDelays } = this.options; + if (retryDelays != null && Object.prototype.toString.call(retryDelays) !== '[object Array]') { + this._emitError(new Error('tus: the `retryDelays` option must either be an array or null')); + return; + } + if (this.options.parallelUploads > 1) { + // Test which options are incompatible with parallel uploads. + if (this.options.uploadUrl != null) { + this._emitError(new Error('tus: cannot use the `uploadUrl` option when parallelUploads is enabled')); + return; + } + if (this.options.uploadSize != null) { + this._emitError(new Error('tus: cannot use the `uploadSize` option when parallelUploads is enabled')); + return; + } + if (this._uploadLengthDeferred) { + this._emitError(new Error('tus: cannot use the `uploadLengthDeferred` option when parallelUploads is enabled')); + return; + } + } + if (this.options.parallelUploadBoundaries) { + if (this.options.parallelUploads <= 1) { + this._emitError(new Error('tus: cannot use the `parallelUploadBoundaries` option when `parallelUploads` is disabled')); + return; + } + if (this.options.parallelUploads !== this.options.parallelUploadBoundaries.length) { + this._emitError(new Error('tus: the `parallelUploadBoundaries` must have the same length as the value of `parallelUploads`')); + return; + } + } + // Note: `start` does not return a Promise or await the preparation on purpose. + // Its supposed to return immediately and start the upload in the background. + this._prepareAndStartUpload().catch((err) => { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + // Errors from the actual upload requests will bubble up to here, where + // we then consider retrying them. Other functions should not call _emitError on their own. + this._retryOrEmitError(err); + }); + } + async _prepareAndStartUpload() { + this._fingerprint = await this.options.fingerprint(this.file, this.options); + if (this._fingerprint == null) { + log('No fingerprint was calculated meaning that the upload cannot be stored in the URL storage.'); + } + else { + log(`Calculated fingerprint: ${this._fingerprint}`); + } + if (this._source == null) { + this._source = await this.options.fileReader.openFile(this.file, this.options.chunkSize); + } + // First, we look at the uploadLengthDeferred option. + // Next, we check if the caller has supplied a manual upload size. + // Finally, we try to use the calculated size from the source object. + if (this._uploadLengthDeferred) { + this._size = null; + } + else if (this.options.uploadSize != null) { + this._size = Number(this.options.uploadSize); + if (Number.isNaN(this._size)) { + throw new Error('tus: cannot convert `uploadSize` option into a number'); + } + } + else { + this._size = this._source.size; + if (this._size == null) { + throw new Error("tus: cannot automatically derive upload's size from input. Specify it manually using the `uploadSize` option or use the `uploadLengthDeferred` option"); + } + } + // If the upload was configured to use multiple requests or if we resume from + // an upload which used multiple requests, we start a parallel upload. + if (this.options.parallelUploads > 1 || this._parallelUploadUrls != null) { + await this._startParallelUpload(); + } + else { + await this._startSingleUpload(); + } + } + /** + * Initiate the uploading procedure for a parallelized upload, where one file is split into + * multiple request which are run in parallel. + * + * @api private + */ + async _startParallelUpload() { + var _a; + const totalSize = this._size; + let totalProgress = 0; + this._parallelUploads = []; + const partCount = this._parallelUploadUrls != null + ? this._parallelUploadUrls.length + : this.options.parallelUploads; + if (this._size == null) { + throw new Error('tus: Expected _size to be set'); + } + // The input file will be split into multiple slices which are uploaded in separate + // requests. Here we get the start and end position for the slices. + const partsBoundaries = (_a = this.options.parallelUploadBoundaries) !== null && _a !== void 0 ? _a : splitSizeIntoParts(this._size, partCount); + // Attach URLs from previous uploads, if available. + const parts = partsBoundaries.map((part, index) => { + var _a; + return ({ + ...part, + uploadUrl: ((_a = this._parallelUploadUrls) === null || _a === void 0 ? void 0 : _a[index]) || null, + }); + }); + // Create an empty list for storing the upload URLs + this._parallelUploadUrls = new Array(parts.length); + // Generate a promise for each slice that will be resolve if the respective + // upload is completed. + const uploads = parts.map(async (part, index) => { + let lastPartProgress = 0; + // @ts-expect-error We know that `_source` is not null here. + const { value } = await this._source.slice(part.start, part.end); + return new Promise((resolve, reject) => { + // Merge with the user supplied options but overwrite some values. + const options = { + ...this.options, + // If available, the partial upload should be resumed from a previous URL. + uploadUrl: part.uploadUrl || null, + // We take manually care of resuming for partial uploads, so they should + // not be stored in the URL storage. + storeFingerprintForResuming: false, + removeFingerprintOnSuccess: false, + // Reset the parallelUploads option to not cause recursion. + parallelUploads: 1, + // Reset this option as we are not doing a parallel upload. + parallelUploadBoundaries: null, + metadata: this.options.metadataForPartialUploads, + // Add the header to indicate the this is a partial upload. + headers: { + ...this.options.headers, + 'Upload-Concat': 'partial', + }, + // Reject or resolve the promise if the upload errors or completes. + onSuccess: resolve, + onError: reject, + // Based in the progress for this partial upload, calculate the progress + // for the entire final upload. + onProgress: (newPartProgress) => { + totalProgress = totalProgress - lastPartProgress + newPartProgress; + lastPartProgress = newPartProgress; + if (totalSize == null) { + throw new Error('tus: Expected totalSize to be set'); + } + this._emitProgress(totalProgress, totalSize); + }, + // Wait until every partial upload has an upload URL, so we can add + // them to the URL storage. + onUploadUrlAvailable: async () => { + // @ts-expect-error We know that _parallelUploadUrls is defined + this._parallelUploadUrls[index] = upload.url; + // Test if all uploads have received an URL + // @ts-expect-error We know that _parallelUploadUrls is defined + if (this._parallelUploadUrls.filter((u) => Boolean(u)).length === parts.length) { + await this._saveUploadInUrlStorage(); + } + }, + }; + if (value == null) { + reject(new Error('tus: no value returned while slicing file for parallel uploads')); + return; + } + // @ts-expect-error `value` is unknown and not an UploadInput + const upload = new BaseUpload(value, options); + upload.start(); + // Store the upload in an array, so we can later abort them if necessary. + // @ts-expect-error We know that _parallelUploadUrls is defined + this._parallelUploads.push(upload); + }); + }); + // Wait until all partial uploads are finished and we can send the POST request for + // creating the final upload. + await Promise.all(uploads); + if (this.options.endpoint == null) { + throw new Error('tus: Expected options.endpoint to be set'); + } + const req = this._openRequest('POST', this.options.endpoint); + req.setHeader('Upload-Concat', `final;${this._parallelUploadUrls.join(' ')}`); + // Add metadata if values have been added + const metadata = encodeMetadata(this.options.metadata); + if (metadata !== '') { + req.setHeader('Upload-Metadata', metadata); + } + let res; + try { + res = await this._sendRequest(req); + } + catch (err) { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + throw new DetailedError('tus: failed to concatenate parallel uploads', err, req, undefined); + } + if (!inStatusCategory(res.getStatus(), 200)) { + throw new DetailedError('tus: unexpected response while creating upload', undefined, req, res); + } + const location = res.getHeader('Location'); + if (location == null) { + throw new DetailedError('tus: invalid or missing Location header', undefined, req, res); + } + if (this.options.endpoint == null) { + throw new Error('tus: Expeced endpoint to be defined.'); + } + this.url = resolveUrl(this.options.endpoint, location); + log(`Created upload at ${this.url}`); + await this._emitSuccess(res); + } + /** + * Initiate the uploading procedure for a non-parallel upload. Here the entire file is + * uploaded in a sequential matter. + * + * @api private + */ + async _startSingleUpload() { + // Reset the aborted flag when the upload is started or else the + // _performUpload will stop before sending a request if the upload has been + // aborted previously. + this._aborted = false; + // The upload had been started previously and we should reuse this URL. + if (this.url != null) { + log(`Resuming upload from previous URL: ${this.url}`); + return await this._resumeUpload(); + } + // A URL has manually been specified, so we try to resume + if (this.options.uploadUrl != null) { + log(`Resuming upload from provided URL: ${this.options.uploadUrl}`); + this.url = this.options.uploadUrl; + return await this._resumeUpload(); + } + // An upload has not started for the file yet, so we start a new one + log('Creating a new upload'); + return await this._createUpload(); + } + /** + * Abort any running request and stop the current upload. After abort is called, no event + * handler will be invoked anymore. You can use the `start` method to resume the upload + * again. + * If `shouldTerminate` is true, the `terminate` function will be called to remove the + * current upload from the server. + * + * @param {boolean} shouldTerminate True if the upload should be deleted from the server. + * @return {Promise} The Promise will be resolved/rejected when the requests finish. + */ + async abort(shouldTerminate = false) { + // Set the aborted flag before any `await`s, so no new requests are started. + this._aborted = true; + // Stop any parallel partial uploads, that have been started in _startParallelUploads. + if (this._parallelUploads != null) { + for (const upload of this._parallelUploads) { + await upload.abort(shouldTerminate); + } + } + // Stop any current running request. + if (this._req != null) { + await this._req.abort(); + // Note: We do not close the file source here, so the user can resume in the future. + } + // Stop any timeout used for initiating a retry. + if (this._retryTimeout != null) { + clearTimeout(this._retryTimeout); + this._retryTimeout = undefined; + } + if (shouldTerminate && this.url != null) { + await terminate(this.url, this.options); + // Remove entry from the URL storage since the upload URL is no longer valid. + await this._removeFromUrlStorage(); + } + } + _emitError(err) { + // Do not emit errors, e.g. from aborted HTTP requests, if the upload has been stopped. + if (this._aborted) + return; + if (typeof this.options.onError === 'function') { + this.options.onError(err); + } + else { + throw err; + } + } + _retryOrEmitError(err) { + // Do not retry if explicitly aborted + if (this._aborted) + return; + // Check if we should retry, when enabled, before sending the error to the user. + if (this.options.retryDelays != null) { + // We will reset the attempt counter if + // - we were already able to connect to the server (offset != null) and + // - we were able to upload a small chunk of data to the server + const shouldResetDelays = this._offset != null && this._offset > this._offsetBeforeRetry; + if (shouldResetDelays) { + this._retryAttempt = 0; + } + if (shouldRetry(err, this._retryAttempt, this.options)) { + const delay = this.options.retryDelays[this._retryAttempt++]; + this._offsetBeforeRetry = this._offset; + this._retryTimeout = setTimeout(() => { + this.start(); + }, delay); + return; + } + } + // If we are not retrying, emit the error to the user. + this._emitError(err); + } + /** + * Publishes notification if the upload has been successfully completed. + * + * @param {object} lastResponse Last HTTP response. + * @api private + */ + async _emitSuccess(lastResponse) { + if (this.options.removeFingerprintOnSuccess) { + // Remove stored fingerprint and corresponding endpoint. This causes + // new uploads of the same file to be treated as a different file. + await this._removeFromUrlStorage(); + } + if (typeof this.options.onSuccess === 'function') { + this.options.onSuccess({ lastResponse }); + } + } + /** + * Publishes notification when data has been sent to the server. This + * data may not have been accepted by the server yet. + * + * @param {number} bytesSent Number of bytes sent to the server. + * @param {number|null} bytesTotal Total number of bytes to be sent to the server. + * @api private + */ + _emitProgress(bytesSent, bytesTotal) { + if (typeof this.options.onProgress === 'function') { + this.options.onProgress(bytesSent, bytesTotal); + } + } + /** + * Publishes notification when a chunk of data has been sent to the server + * and accepted by the server. + * @param {number} chunkSize Size of the chunk that was accepted by the server. + * @param {number} bytesAccepted Total number of bytes that have been + * accepted by the server. + * @param {number|null} bytesTotal Total number of bytes to be sent to the server. + * @api private + */ + _emitChunkComplete(chunkSize, bytesAccepted, bytesTotal) { + if (typeof this.options.onChunkComplete === 'function') { + this.options.onChunkComplete(chunkSize, bytesAccepted, bytesTotal); + } + } + /** + * Create a new upload using the creation extension by sending a POST + * request to the endpoint. After successful creation the file will be + * uploaded + * + * @api private + */ + async _createUpload() { + if (!this.options.endpoint) { + throw new Error('tus: unable to create upload because no endpoint is provided'); + } + const req = this._openRequest('POST', this.options.endpoint); + if (this._uploadLengthDeferred) { + req.setHeader('Upload-Defer-Length', '1'); + } + else { + if (this._size == null) { + throw new Error('tus: expected _size to be set'); + } + req.setHeader('Upload-Length', `${this._size}`); + } + // Add metadata if values have been added + const metadata = encodeMetadata(this.options.metadata); + if (metadata !== '') { + req.setHeader('Upload-Metadata', metadata); + } + let res; + try { + if (this.options.uploadDataDuringCreation && !this._uploadLengthDeferred) { + this._offset = 0; + res = await this._addChunkToRequest(req); + } + else { + if (this.options.protocol === PROTOCOL_IETF_DRAFT_03 || + this.options.protocol === PROTOCOL_IETF_DRAFT_05) { + req.setHeader('Upload-Complete', '?0'); + } + res = await this._sendRequest(req); + } + } + catch (err) { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + throw new DetailedError('tus: failed to create upload', err, req, undefined); + } + if (!inStatusCategory(res.getStatus(), 200)) { + throw new DetailedError('tus: unexpected response while creating upload', undefined, req, res); + } + const location = res.getHeader('Location'); + if (location == null) { + throw new DetailedError('tus: invalid or missing Location header', undefined, req, res); + } + if (this.options.endpoint == null) { + throw new Error('tus: Expected options.endpoint to be set'); + } + this.url = resolveUrl(this.options.endpoint, location); + log(`Created upload at ${this.url}`); + if (typeof this.options.onUploadUrlAvailable === 'function') { + await this.options.onUploadUrlAvailable(); + } + if (this._size === 0) { + // Nothing to upload and file was successfully created + await this._emitSuccess(res); + if (this._source) + this._source.close(); + return; + } + await this._saveUploadInUrlStorage(); + if (this.options.uploadDataDuringCreation) { + await this._handleUploadResponse(req, res); + } + else { + this._offset = 0; + await this._performUpload(); + } + } + /** + * Try to resume an existing upload. First a HEAD request will be sent + * to retrieve the offset. If the request fails a new upload will be + * created. In the case of a successful response the file will be uploaded. + * + * @api private + */ + async _resumeUpload() { + if (this.url == null) { + throw new Error('tus: Expected url to be set'); + } + const req = this._openRequest('HEAD', this.url); + let res; + try { + res = await this._sendRequest(req); + } + catch (err) { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + throw new DetailedError('tus: failed to resume upload', err, req, undefined); + } + const status = res.getStatus(); + if (!inStatusCategory(status, 200)) { + // If the upload is locked (indicated by the 423 Locked status code), we + // emit an error instead of directly starting a new upload. This way the + // retry logic can catch the error and will retry the upload. An upload + // is usually locked for a short period of time and will be available + // afterwards. + if (status === 423) { + throw new DetailedError('tus: upload is currently locked; retry later', undefined, req, res); + } + if (inStatusCategory(status, 400)) { + // Remove stored fingerprint and corresponding endpoint, + // on client errors since the file can not be found + await this._removeFromUrlStorage(); + } + if (!this.options.endpoint) { + // Don't attempt to create a new upload if no endpoint is provided. + throw new DetailedError('tus: unable to resume upload (new upload cannot be created without an endpoint)', undefined, req, res); + } + // Try to create a new upload + this.url = null; + await this._createUpload(); + } + const offsetStr = res.getHeader('Upload-Offset'); + if (offsetStr === undefined) { + throw new DetailedError('tus: missing Upload-Offset header', undefined, req, res); + } + const offset = Number.parseInt(offsetStr, 10); + if (Number.isNaN(offset)) { + throw new DetailedError('tus: invalid Upload-Offset header', undefined, req, res); + } + const deferLength = res.getHeader('Upload-Defer-Length'); + this._uploadLengthDeferred = deferLength === '1'; + // @ts-expect-error parseInt also handles undefined as we want it to + const length = Number.parseInt(res.getHeader('Upload-Length'), 10); + if (Number.isNaN(length) && + !this._uploadLengthDeferred && + this.options.protocol === PROTOCOL_TUS_V1) { + throw new DetailedError('tus: invalid or missing length value', undefined, req, res); + } + if (typeof this.options.onUploadUrlAvailable === 'function') { + await this.options.onUploadUrlAvailable(); + } + await this._saveUploadInUrlStorage(); + // Upload has already been completed and we do not need to send additional + // data to the server + if (offset === length) { + this._emitProgress(length, length); + await this._emitSuccess(res); + return; + } + this._offset = offset; + await this._performUpload(); + } + /** + * Start uploading the file using PATCH requests. The file will be divided + * into chunks as specified in the chunkSize option. During the upload + * the onProgress event handler may be invoked multiple times. + * + * @api private + */ + async _performUpload() { + // If the upload has been aborted, we will not send the next PATCH request. + // This is important if the abort method was called during a callback, such + // as onChunkComplete or onProgress. + if (this._aborted) { + return; + } + let req; + if (this.url == null) { + throw new Error('tus: Expected url to be set'); + } + // Some browser and servers may not support the PATCH method. For those + // cases, you can tell tus-js-client to use a POST request with the + // X-HTTP-Method-Override header for simulating a PATCH request. + if (this.options.overridePatchMethod) { + req = this._openRequest('POST', this.url); + req.setHeader('X-HTTP-Method-Override', 'PATCH'); + } + else { + req = this._openRequest('PATCH', this.url); + } + req.setHeader('Upload-Offset', `${this._offset}`); + let res; + try { + res = await this._addChunkToRequest(req); + } + catch (err) { + // Don't emit an error if the upload was aborted manually + if (this._aborted) { + return; + } + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + throw new DetailedError(`tus: failed to upload chunk at offset ${this._offset}`, err, req, undefined); + } + if (!inStatusCategory(res.getStatus(), 200)) { + throw new DetailedError('tus: unexpected response while uploading chunk', undefined, req, res); + } + await this._handleUploadResponse(req, res); + } + /** + * _addChunktoRequest reads a chunk from the source and sends it using the + * supplied request object. It will not handle the response. + * + * @api private + */ + async _addChunkToRequest(req) { + const start = this._offset; + let end = this._offset + this.options.chunkSize; + req.setProgressHandler((bytesSent) => { + this._emitProgress(start + bytesSent, this._size); + }); + if (this.options.protocol === PROTOCOL_TUS_V1) { + req.setHeader('Content-Type', 'application/offset+octet-stream'); + } + else if (this.options.protocol === PROTOCOL_IETF_DRAFT_05) { + req.setHeader('Content-Type', 'application/partial-upload'); + } + // The specified chunkSize may be Infinity or the calcluated end position + // may exceed the file's size. In both cases, we limit the end position to + // the input's total size for simpler calculations and correctness. + if ( + // @ts-expect-error _size is set here + (end === Number.POSITIVE_INFINITY || end > this._size) && + !this._uploadLengthDeferred) { + // @ts-expect-error _size is set here + end = this._size; + } + // TODO: What happens if abort is called during slice? + // @ts-expect-error _source is set here + const { value, size, done } = await this._source.slice(start, end); + const sizeOfValue = size !== null && size !== void 0 ? size : 0; + // If the upload length is deferred, the upload size was not specified during + // upload creation. So, if the file reader is done reading, we know the total + // upload size and can tell the tus server. + if (this._uploadLengthDeferred && done) { + this._size = this._offset + sizeOfValue; + req.setHeader('Upload-Length', `${this._size}`); + this._uploadLengthDeferred = false; + } + // The specified uploadSize might not match the actual amount of data that a source + // provides. In these cases, we cannot successfully complete the upload, so we + // rather error out and let the user know. If not, tus-js-client will be stuck + // in a loop of repeating empty PATCH requests. + // See https://community.transloadit.com/t/how-to-abort-hanging-companion-uploads/16488/13 + const newSize = this._offset + sizeOfValue; + if (!this._uploadLengthDeferred && done && newSize !== this._size) { + throw new Error(`upload was configured with a size of ${this._size} bytes, but the source is done after ${newSize} bytes`); + } + if (value == null) { + return await this._sendRequest(req); + } + if (this.options.protocol === PROTOCOL_IETF_DRAFT_03 || + this.options.protocol === PROTOCOL_IETF_DRAFT_05) { + req.setHeader('Upload-Complete', done ? '?1' : '?0'); + } + this._emitProgress(this._offset, this._size); + return await this._sendRequest(req, value); + } + /** + * _handleUploadResponse is used by requests that haven been sent using _addChunkToRequest + * and already have received a response. + * + * @api private + */ + async _handleUploadResponse(req, res) { + // TODO: || '' is not very good. + const offset = Number.parseInt(res.getHeader('Upload-Offset') || '', 10); + if (Number.isNaN(offset)) { + throw new DetailedError('tus: invalid or missing offset value', undefined, req, res); + } + this._emitProgress(offset, this._size); + this._emitChunkComplete(offset - this._offset, offset, this._size); + this._offset = offset; + if (offset === this._size) { + // Yay, finally done :) + await this._emitSuccess(res); + if (this._source) + this._source.close(); + return; + } + await this._performUpload(); + } + /** + * Create a new HTTP request object with the given method and URL. + * + * @api private + */ + _openRequest(method, url) { + const req = openRequest(method, url, this.options); + this._req = req; + return req; + } + /** + * Remove the entry in the URL storage, if it has been saved before. + * + * @api private + */ + async _removeFromUrlStorage() { + if (!this._urlStorageKey) + return; + await this.options.urlStorage.removeUpload(this._urlStorageKey); + this._urlStorageKey = undefined; + } + /** + * Add the upload URL to the URL storage, if possible. + * + * @api private + */ + async _saveUploadInUrlStorage() { + // We do not store the upload URL + // - if it was disabled in the option, or + // - if no fingerprint was calculated for the input (i.e. a stream), or + // - if the URL is already stored (i.e. key is set alread). + if (!this.options.storeFingerprintForResuming || + !this._fingerprint || + this._urlStorageKey != null) { + return; + } + const storedUpload = { + size: this._size, + metadata: this.options.metadata, + creationTime: new Date().toString(), + urlStorageKey: this._fingerprint, + }; + if (this._parallelUploads) { + // Save multiple URLs if the parallelUploads option is used ... + storedUpload.parallelUploadUrls = this._parallelUploadUrls; + } + else { + // ... otherwise we just save the one available URL. + // @ts-expect-error We still have to figure out the null/undefined situation. + storedUpload.uploadUrl = this.url; + } + const urlStorageKey = await this.options.urlStorage.addUpload(this._fingerprint, storedUpload); + // TODO: Emit a waring if urlStorageKey is undefined. Should we even allow this? + this._urlStorageKey = urlStorageKey; + } + /** + * Send a request with the provided body. + * + * @api private + */ + _sendRequest(req, body) { + return sendRequest(req, body, this.options); + } +} +function encodeMetadata(metadata) { + return Object.entries(metadata) + .map(([key, value]) => `${key} ${Base64.encode(String(value))}`) + .join(','); +} +/** + * Checks whether a given status is in the range of the expected category. + * For example, only a status between 200 and 299 will satisfy the category 200. + * + * @api private + */ +function inStatusCategory(status, category) { + return status >= category && status < category + 100; +} +/** + * Create a new HTTP request with the specified method and URL. + * The necessary headers that are included in every request + * will be added, including the request ID. + * + * @api private + */ +function openRequest(method, url, options) { + const req = options.httpStack.createRequest(method, url); + if (options.protocol === PROTOCOL_IETF_DRAFT_03) { + req.setHeader('Upload-Draft-Interop-Version', '5'); + } + else if (options.protocol === PROTOCOL_IETF_DRAFT_05) { + req.setHeader('Upload-Draft-Interop-Version', '6'); + } + else { + req.setHeader('Tus-Resumable', '1.0.0'); + } + const headers = options.headers || {}; + for (const [name, value] of Object.entries(headers)) { + req.setHeader(name, value); + } + if (options.addRequestId) { + const requestId = uuid(); + req.setHeader('X-Request-ID', requestId); + } + return req; +} +/** + * Send a request with the provided body while invoking the onBeforeRequest + * and onAfterResponse callbacks. + * + * @api private + */ +async function sendRequest(req, body, options) { + if (typeof options.onBeforeRequest === 'function') { + await options.onBeforeRequest(req); + } + const res = await req.send(body); + if (typeof options.onAfterResponse === 'function') { + await options.onAfterResponse(req, res); + } + return res; +} +/** + * Checks whether the browser running this code has internet access. + * This function will always return true in the node.js environment + * TODO: Move this into a browser-specific location. + * + * @api private + */ +function isOnline() { + let online = true; + // Note: We don't reference `window` here because the navigator object also exists + // in a Web Worker's context. + // -disable-next-line no-undef + if (typeof navigator !== 'undefined' && navigator.onLine === false) { + online = false; + } + return online; +} +/** + * Checks whether or not it is ok to retry a request. + * @param {Error|DetailedError} err the error returned from the last request + * @param {number} retryAttempt the number of times the request has already been retried + * @param {object} options tus Upload options + * + * @api private + */ +function shouldRetry(err, retryAttempt, options) { + // We only attempt a retry if + // - retryDelays option is set + // - we didn't exceed the maxium number of retries, yet, and + // - this error was caused by a request or it's response and + // - the error is server error (i.e. not a status 4xx except a 409 or 423) or + // a onShouldRetry is specified and returns true + // - the browser does not indicate that we are offline + const isNetworkError = 'originalRequest' in err && err.originalRequest != null; + if (options.retryDelays == null || + retryAttempt >= options.retryDelays.length || + !isNetworkError) { + return false; + } + if (options && typeof options.onShouldRetry === 'function') { + return options.onShouldRetry(err, retryAttempt, options); + } + return defaultOnShouldRetry(err); +} +/** + * determines if the request should be retried. Will only retry if not a status 4xx except a 409 or 423 + * @param {DetailedError} err + * @returns {boolean} + */ +function defaultOnShouldRetry(err) { + const status = err.originalResponse ? err.originalResponse.getStatus() : 0; + return (!inStatusCategory(status, 400) || status === 409 || status === 423) && isOnline(); +} +/** + * Resolve a relative link given the origin as source. For example, + * if a HTTP request to http://example.com/files/ returns a Location + * header with the value /upload/abc, the resolved URL will be: + * http://example.com/upload/abc + */ +function resolveUrl(origin, link) { + return new URL(link, origin).toString(); +} +/** + * Calculate the start and end positions for the parts if an upload + * is split into multiple parallel requests. + * + * @param {number} totalSize The byte size of the upload, which will be split. + * @param {number} partCount The number in how many parts the upload will be split. + * @return {Part[]} + * @api private + */ +function splitSizeIntoParts(totalSize, partCount) { + const partSize = Math.floor(totalSize / partCount); + const parts = []; + for (let i = 0; i < partCount; i++) { + parts.push({ + start: partSize * i, + end: partSize * (i + 1), + }); + } + parts[partCount - 1].end = totalSize; + return parts; +} +async function wait(delay) { + return new Promise((resolve) => { + setTimeout(resolve, delay); + }); +} +/** + * Use the Termination extension to delete an upload from the server by sending a DELETE + * request to the specified upload URL. This is only possible if the server supports the + * Termination extension. If the `options.retryDelays` property is set, the method will + * also retry if an error ocurrs. + * + * @param {String} url The upload's URL which will be terminated. + * @param {object} options Optional options for influencing HTTP requests. + * @return {Promise} The Promise will be resolved/rejected when the requests finish. + */ +export async function terminate(url, options) { + const req = openRequest('DELETE', url, options); + try { + const res = await sendRequest(req, undefined, options); + // A 204 response indicates a successfull request + if (res.getStatus() === 204) { + return; + } + throw new DetailedError('tus: unexpected response while terminating upload', undefined, req, res); + } + catch (err) { + if (!(err instanceof Error)) { + throw new Error(`tus: value thrown that is not an error: ${err}`); + } + const detailedErr = err instanceof DetailedError + ? err + : new DetailedError('tus: failed to terminate upload', err, req); + if (!shouldRetry(detailedErr, 0, options)) { + throw detailedErr; + } + // Instead of keeping track of the retry attempts, we remove the first element from the delays + // array. If the array is empty, all retry attempts are used up and we will bubble up the error. + // We recursively call the terminate function will removing elements from the retryDelays array. + const delay = options.retryDelays[0]; + const remainingDelays = options.retryDelays.slice(1); + const newOptions = { + ...options, + retryDelays: remainingDelays, + }; + await wait(delay); + await terminate(url, newOptions); + } +} +//# sourceMappingURL=upload.js.map \ No newline at end of file diff --git a/lib.esm/upload.js.map b/lib.esm/upload.js.map new file mode 100644 index 00000000..f0d3fd4a --- /dev/null +++ b/lib.esm/upload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"upload.js","sourceRoot":"","sources":["../lib/upload.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib.esm/uuid.d.ts b/lib.esm/uuid.d.ts new file mode 100644 index 00000000..1866c309 --- /dev/null +++ b/lib.esm/uuid.d.ts @@ -0,0 +1,13 @@ +/** + * Generate a UUID v4 based on random numbers. We intentioanlly use the less + * secure Math.random function here since the more secure crypto.getRandomNumbers + * is not available on all platforms. + * This is not a problem for us since we use the UUID only for generating a + * request ID, so we can correlate server logs to client errors. + * + * This function is taken from following site: + * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript + * + * @return {string} The generate UUID + */ +export declare function uuid(): string; diff --git a/lib.esm/uuid.js b/lib.esm/uuid.js new file mode 100644 index 00000000..b6eeaf7d --- /dev/null +++ b/lib.esm/uuid.js @@ -0,0 +1,20 @@ +/** + * Generate a UUID v4 based on random numbers. We intentioanlly use the less + * secure Math.random function here since the more secure crypto.getRandomNumbers + * is not available on all platforms. + * This is not a problem for us since we use the UUID only for generating a + * request ID, so we can correlate server logs to client errors. + * + * This function is taken from following site: + * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript + * + * @return {string} The generate UUID + */ +export function uuid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} +//# sourceMappingURL=uuid.js.map \ No newline at end of file diff --git a/lib.esm/uuid.js.map b/lib.esm/uuid.js.map new file mode 100644 index 00000000..c0ee30f4 --- /dev/null +++ b/lib.esm/uuid.js.map @@ -0,0 +1 @@ +{"version":3,"file":"uuid.js","sourceRoot":"","sources":["../lib/uuid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,IAAI;IAClB,OAAO,sCAAsC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;QACnE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAA;QACzC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC"} \ No newline at end of file