From 5ce98f3abf2a4e744865ccac9bce4d64a5119dda Mon Sep 17 00:00:00 2001 From: Borewit Date: Sun, 8 Sep 2024 13:16:42 +0200 Subject: [PATCH] Add ability to abort async operations (#667) Co-authored-by: Sindre Sorhus --- core.d.ts | 2 +- core.js | 8 +++++--- index.js | 2 +- package.json | 2 +- readme.md | 16 ++++++++++++++++ test.js | 20 ++++++++++++++++++++ 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/core.d.ts b/core.d.ts index 1f1ad94f..1768cc04 100644 --- a/core.d.ts +++ b/core.d.ts @@ -491,7 +491,7 @@ export function fileTypeStream(webStream: AnyWebReadableStream, opti export declare class FileTypeParser { detectors: Iterable; - constructor(options?: {customDetectors?: Iterable}); + constructor(options?: {customDetectors?: Iterable; signal: AbortSignal}); /** Works the same way as {@link fileTypeFromBuffer}, additionally taking into account custom detectors (if any were provided to the constructor). diff --git a/core.js b/core.js index 8f4aa425..fdaa4d00 100644 --- a/core.js +++ b/core.js @@ -58,7 +58,9 @@ export async function fileTypeStream(webStream, options) { export class FileTypeParser { constructor(options) { this.detectors = options?.customDetectors; - + this.tokenizerOptions = { + abortSignal: options?.signal, + }; this.fromTokenizer = this.fromTokenizer.bind(this); this.fromBuffer = this.fromBuffer.bind(this); this.parse = this.parse.bind(this); @@ -92,7 +94,7 @@ export class FileTypeParser { return; } - return this.fromTokenizer(strtok3.fromBuffer(buffer)); + return this.fromTokenizer(strtok3.fromBuffer(buffer, this.tokenizerOptions)); } async fromBlob(blob) { @@ -100,7 +102,7 @@ export class FileTypeParser { } async fromStream(stream) { - const tokenizer = await strtok3.fromWebStream(stream); + const tokenizer = await strtok3.fromWebStream(stream, this.tokenizerOptions); try { return await this.fromTokenizer(tokenizer); } finally { diff --git a/index.js b/index.js index 17d3d534..2dcea75e 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,7 @@ import {FileTypeParser, reasonableDetectionSizeInBytes} from './core.js'; export class NodeFileTypeParser extends FileTypeParser { async fromStream(stream) { - const tokenizer = await (stream instanceof WebReadableStream ? strtok3.fromWebStream(stream) : strtok3.fromStream(stream)); + const tokenizer = await (stream instanceof WebReadableStream ? strtok3.fromWebStream(stream, this.tokenizerOptions) : strtok3.fromStream(stream, this.tokenizerOptions)); try { return super.fromTokenizer(tokenizer); } finally { diff --git a/package.json b/package.json index 25076026..16e10a3b 100644 --- a/package.json +++ b/package.json @@ -220,7 +220,7 @@ ], "dependencies": { "get-stream": "^9.0.1", - "strtok3": "^8.1.0", + "strtok3": "^9.0.0", "token-types": "^6.0.0", "uint8array-extras": "^1.3.0" }, diff --git a/readme.md b/readme.md index 5ef754cf..f1bed47b 100644 --- a/readme.md +++ b/readme.md @@ -367,6 +367,22 @@ const fileType = await parser.fromBuffer(buffer); console.log(fileType); ``` +## Abort signal + +Some async operations can be aborted by passing an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to the `FileTypeParser` constructor. + +```js +import {FileTypeParser} from 'file-type'; // or `NodeFileTypeParser` in Node.js + +const abortController = new AbortController() + +const parser = new FileTypeParser({abortSignal: abortController.signal}); + +const promise = parser.fromStream(blob.stream()); + +abortController.abort(); // Abort file-type reading from the Blob stream. +``` + ## Supported file types - [`3g2`](https://en.wikipedia.org/wiki/3GP_and_3G2#3G2) - Multimedia container format defined by the 3GPP2 for 3G CDMA2000 multimedia services diff --git a/test.js b/test.js index 3b937533..b76651af 100644 --- a/test.js +++ b/test.js @@ -466,6 +466,26 @@ test('.fileTypeStream() method - sampleSize option', async t => { t.is(stream.fileType.mime, 'video/ogg'); }); +test('.fileTypeFromStream() method - be able to abort operation', async t => { + const bufferA = new Uint8Array([0, 1, 0, 1]); + class MyStream extends stream.Readable { + _read() { + setTimeout(() => { + this.push(bufferA); + this.push(null); + }, 500); + } + } + + const shortStream = new MyStream(); + const abortController = new AbortController(); + const parser = new NodeFileTypeParser({signal: abortController.signal}); + const promiseFileType = parser.fromStream(shortStream); + abortController.abort(); // Abort asynchronous operation: reading from shortStream + const error = await t.throwsAsync(promiseFileType); + t.is(error.message, 'Stream closed'); +}); + test('supportedExtensions.has', t => { t.true(supportedExtensions.has('jpg')); t.false(supportedExtensions.has('blah'));