diff --git a/.changeset/warm-birds-glow.md b/.changeset/warm-birds-glow.md new file mode 100644 index 0000000000..9afff663a3 --- /dev/null +++ b/.changeset/warm-birds-glow.md @@ -0,0 +1,5 @@ +--- +'posthog-js': patch +--- + +fix: wrap sendBeacon body in Blob to ensure Content-Type header is set diff --git a/packages/browser/src/__tests__/request.test.ts b/packages/browser/src/__tests__/request.test.ts index f217c049d6..aebd76bc20 100644 --- a/packages/browser/src/__tests__/request.test.ts +++ b/packages/browser/src/__tests__/request.test.ts @@ -502,11 +502,15 @@ describe('request', () => { expect(mockedNavigator?.sendBeacon).toHaveBeenCalledWith( 'https://any.posthog-instance.com/?_=1700000000000&ver=1.23.45&compression=gzip-js&beacon=1', - expect.any(ArrayBuffer) + expect.any(Blob) ) - const arrayBuffer = mockedNavigator?.sendBeacon.mock.calls[0][1] as ArrayBuffer - - const result = new TextDecoder().decode(arrayBuffer) + const blob = mockedNavigator?.sendBeacon.mock.calls[0][1] as Blob + expect(blob.type).toBe('text/plain') + const result = await new Promise((resolve) => { + const reader = new FileReader() + reader.onload = () => resolve(reader.result as string) + reader.readAsText(blob) + }) expect(result).toMatchInlineSnapshot(` "��VJ��W�RJJ,R���+� diff --git a/packages/browser/src/request.ts b/packages/browser/src/request.ts index a35cf4b355..b72bda98cc 100644 --- a/packages/browser/src/request.ts +++ b/packages/browser/src/request.ts @@ -255,8 +255,13 @@ const _sendBeacon = (options: RequestWithOptions) => { try { const { contentType, body } = encodePostData(options) ?? {} - // sendBeacon requires a blob so we convert it - const sendBeaconBody = typeof body === 'string' ? new Blob([body], { type: contentType }) : body + if (!body) { + return + } + // sendBeacon requires a Blob to set the Content-Type header correctly. + // Without wrapping, ArrayBuffer bodies are sent with no Content-Type, + // which can cause issues with proxies/WAFs that require it. + const sendBeaconBody = body instanceof Blob ? body : new Blob([body], { type: contentType }) navigator!.sendBeacon!(url, sendBeaconBody) } catch { // send beacon is a best-effort, fire-and-forget mechanism on page unload,