From dabf0413dfb12d3ffa93ef06700581d0f3de67a0 Mon Sep 17 00:00:00 2001 From: cb-alish Date: Wed, 3 Jun 2026 12:15:30 +0530 Subject: [PATCH] Releasing v3.24.1 --- CHANGELOG.md | 10 +++++++++ VERSION | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/RequestWrapper.ts | 2 +- src/environment.ts | 2 +- test/requestWrapper.test.ts | 42 +++++++++++++++++++++++++++++++++++++ 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d73ab..5c7d67e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### v3.24.1 (2026-06-03) +* * * +### Bug Fixes: +- Fixed `Content-Length` header being computed from the JavaScript string length (UTF-16 code units) instead of the UTF-8 byte length. Requests with non-ASCII payloads (e.g. accented characters, CJK, emoji) under-declared `Content-Length` and were rejected by Node's `fetch`/undici (≥ 7.26.0) with `Request body length does not match content-length header`. The header is now computed via `Buffer.byteLength(data, 'utf8')`. Resolves https://github.com/chargebee/chargebee-node/issues/119. + +### Tests: +- ASCII form-urlencoded body — `Content-Length` matches the UTF-8 byte length of the serialized body. +- Multi-byte JSON body — `Content-Length` matches the UTF-8 byte length and is greater than the JS character count, ensuring the regression cannot return. + + ### v3.24.0 (2026-05-04) * * * ### New Resources: diff --git a/VERSION b/VERSION index 954e228..455cf2c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.24.0 +3.24.1 diff --git a/package-lock.json b/package-lock.json index 203c313..c3a8724 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "chargebee", - "version": "3.24.0", + "version": "3.24.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chargebee", - "version": "3.24.0", + "version": "3.24.1", "devDependencies": { "@types/chai": "^4.3.5", "@types/mocha": "^10.0.10", diff --git a/package.json b/package.json index 7a2889e..f25bbe5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chargebee", - "version": "3.24.0", + "version": "3.24.1", "description": "A library for integrating with Chargebee.", "scripts": { "prepack": "npm install && npm run build", diff --git a/src/RequestWrapper.ts b/src/RequestWrapper.ts index 878304c..b3e004e 100644 --- a/src/RequestWrapper.ts +++ b/src/RequestWrapper.ts @@ -122,7 +122,7 @@ export class RequestWrapper { const requestHeaders: RequestHeaders = { ...this.httpHeaders }; if (data && data.length) { extend(true, requestHeaders, { - 'Content-Length': data.length, + 'Content-Length': Buffer.byteLength(data, 'utf8'), }); } diff --git a/src/environment.ts b/src/environment.ts index 09dc710..1de9b8f 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -9,7 +9,7 @@ export const Environment = { hostSuffix: '.chargebee.com', apiPath: '/api/v2', timeout: DEFAULT_TIME_OUT, - clientVersion: 'v3.24.0', + clientVersion: 'v3.24.1', port: DEFAULT_PORT, timemachineWaitInMillis: DEFAULT_TIME_MACHINE_WAIT, exportWaitInMillis: DEFAULT_EXPORT_WAIT, diff --git a/test/requestWrapper.test.ts b/test/requestWrapper.test.ts index 0d67057..d63cdb1 100644 --- a/test/requestWrapper.test.ts +++ b/test/requestWrapper.test.ts @@ -189,6 +189,48 @@ describe('RequestWrapper - request headers', () => { }); }); + describe('Content-Length header', () => { + it('should set Content-Length to the UTF-8 byte length for ASCII form-urlencoded bodies', async () => { + responseFactory = () => + new Response(JSON.stringify({ customer: { id: 'cust_123' } }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + + const chargebee = createChargebee(); + await chargebee.customer.create({ first_name: 'John' }); + + const body = await capturedRequests[0].text(); + const expected = Buffer.byteLength(body, 'utf8'); + expect(capturedRequests[0].headers.get('Content-Length')).to.equal( + String(expected), + ); + }); + + it('should set Content-Length to the UTF-8 byte length (not character count) for multi-byte JSON bodies', async () => { + responseFactory = () => + new Response(JSON.stringify({ personalized_offers: [] }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + + const chargebee = createChargebee(); + await chargebee.personalizedOffer.personalizedOffers({ + customer_id: 'cust_123', + first_name: 'Jürgen', + last_name: 'Müller — 🎉', + }); + + const body = await capturedRequests[0].text(); + const byteLength = Buffer.byteLength(body, 'utf8'); + const charLength = body.length; + expect(byteLength).to.be.greaterThan(charLength); + expect(capturedRequests[0].headers.get('Content-Length')).to.equal( + String(byteLength), + ); + }); + }); + describe('Lang-Version header', () => { it('should set Lang-Version to the current Node.js process.version', async () => { const chargebee = createChargebee();