From f94e1da992e2e7f0d1633c6592c46d8cf526eada Mon Sep 17 00:00:00 2001 From: "raul@facturapi.io" Date: Mon, 13 Apr 2026 14:11:38 -0600 Subject: [PATCH] fix(responseBlob): adequate response if content is octet-stream --- CHANGELOG.md | 4 +++ src/wrapper.ts | 87 ++++++++++++++++++++++++++------------------------ 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b91eb8c..c0cc9eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [4.14.3] 2026-04-13 +### Fixed +- Return blob if content-type is octet-stream. This is the coverage for zip files + ## [4.14.2] 2026-03-31 ### Fixed - Add `TaxFactor.EXENTO` (`"Exento"`) to align SDK enums with the API/OpenAPI allowed tax factor values. diff --git a/src/wrapper.ts b/src/wrapper.ts index a14f366..8a85b64 100644 --- a/src/wrapper.ts +++ b/src/wrapper.ts @@ -48,50 +48,55 @@ const responseInterceptor = async (response: Response) => { throw new Error(jsonMessage || bodyText || response.statusText); } - const contentType = response.headers.get('content-type'); - if (contentType) { - if ( - contentType.includes('image/') || - contentType.includes('application/pdf') || - contentType.includes('application/xml') || - contentType.includes('application/zip') - ) { - if (hasBuffer()) { - const reader = response.body?.getReader(); - if (!reader) { - return response.blob(); - } - try { - const { Readable } = await import('stream'); - return new Readable({ - read() { - reader.read() - .then(({ done, value }) => { - if (done) { - this.push(null); // end stream - } else { - this.push(Buffer.from(value)); // push data to stream - } - }) - .catch((error: unknown) => { - void reader.cancel(error).catch(() => undefined); - this.destroy( - error instanceof Error - ? error - : new Error('Failed to read binary response stream'), - ); - }); - }, - }); - } catch (e) { - return response.blob(); - } - } else { + const contentType = response.headers.get('content-type') || ''; + const contentDisposition = + response.headers.get('content-disposition') || ''; + const looksLikeZip = /filename=.*\.zip\b/i.test(contentDisposition); + const looksLikeDownload = + /attachment/i.test(contentDisposition) || looksLikeZip; + const isBinaryContentType = + contentType.includes('image/') || + contentType.includes('application/pdf') || + contentType.includes('application/xml') || + contentType.includes('application/zip') || + contentType.includes('application/octet-stream'); + + if (isBinaryContentType || !contentType || looksLikeDownload) { + if (hasBuffer()) { + const reader = response.body?.getReader(); + if (!reader) { + return response.blob(); + } + try { + const { Readable } = await import('stream'); + return new Readable({ + read() { + reader.read() + .then(({ done, value }) => { + if (done) { + this.push(null); // end stream + } else { + this.push(Buffer.from(value)); // push data to stream + } + }) + .catch((error: unknown) => { + void reader.cancel(error).catch(() => undefined); + this.destroy( + error instanceof Error + ? error + : new Error('Failed to read binary response stream'), + ); + }); + }, + }); + } catch (e) { return response.blob(); } - } else if (contentType.includes('application/json')) { - return response.json(); + } else { + return response.blob(); } + } else if (contentType.includes('application/json')) { + return response.json(); } return response.text(); };