From f1c2d6ce4095a4003ab45a828122f07b172da656 Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Thu, 22 Oct 2026 07:36:29 +0000 Subject: [PATCH] fix(recorder-core): declare final Drive size at part boundaries --- .../instant-recording-uploader.test.ts | 76 +++++++++++++++++++ .../recorder-core/src/instant-mp4-uploader.ts | 9 ++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/packages/recorder-core/__tests__/instant-recording-uploader.test.ts b/packages/recorder-core/__tests__/instant-recording-uploader.test.ts index 45334fa20da..b1255f39a91 100644 --- a/packages/recorder-core/__tests__/instant-recording-uploader.test.ts +++ b/packages/recorder-core/__tests__/instant-recording-uploader.test.ts @@ -466,6 +466,82 @@ describe("InstantRecordingUploader", () => { ); }); + it.each([ + [DRIVE_PART_BYTES - 1, false], + [DRIVE_PART_BYTES, false], + [DRIVE_PART_BYTES, true], + [DRIVE_PART_BYTES + 1, false], + [2 * DRIVE_PART_BYTES, false], + [2 * DRIVE_PART_BYTES, true], + ] as const)( + "declares the final Drive size for %i streamed bytes, final Blob: %s", + async (totalBytes, withFinalBlob) => { + const partCount = Math.ceil(totalBytes / DRIVE_PART_BYTES); + const fetchMock = vi.fn( + async (input: RequestInfo | URL, init?: RequestInit) => { + if (input.toString().endsWith("/presign-part")) { + return makeJsonResponse({ + presignedUrl: + "https://www.googleapis.com/upload/drive/v3/files/session", + provider: "googleDrive", + }); + } + expect(input.toString()).toBe("/api/upload/multipart/complete"); + const body = JSON.parse(String(init?.body)); + expect(body.parts).toEqual( + Array.from({ length: partCount }, (_, index) => ({ + partNumber: index + 1, + etag: `drive-${index + 1}`, + size: Math.min( + DRIVE_PART_BYTES, + totalBytes - index * DRIVE_PART_BYTES, + ), + })), + ); + return makeJsonResponse({ success: true }); + }, + ); + vi.stubGlobal("fetch", fetchMock); + MockXMLHttpRequest.setOutcomes( + Array.from({ length: partCount }, (_, index) => ({ + type: "success", + status: index === partCount - 1 ? 200 : 308, + })), + ); + const uploader = new InstantRecordingUploader({ + videoId, + uploadId: "drive-session", + provider: "googleDrive", + mimeType: "video/webm", + subpath: "raw-upload.webm", + setUploadStatus: vi.fn(), + sendProgressUpdate: vi.fn().mockResolvedValue(undefined), + }); + const blob = makeBlob(totalBytes, "video/webm"); + uploader.handleChunk(blob, totalBytes); + await new Promise((resolve) => setImmediate(resolve)); + + const streamedPartCount = MockXMLHttpRequest.recordedParts.length; + await uploader.finalize({ + finalBlob: withFinalBlob ? blob : undefined, + durationSeconds: 20, + subpath: "raw-upload.webm", + }); + + expect( + MockXMLHttpRequest.recordedHeaders.map((headers) => + headers.get("content-range"), + ), + ).toEqual( + Array.from({ length: partCount }, (_, index) => { + const end = Math.min((index + 1) * DRIVE_PART_BYTES, totalBytes); + return `bytes ${index * DRIVE_PART_BYTES}-${end - 1}/${end === totalBytes ? totalBytes : "*"}`; + }), + ); + expect(streamedPartCount).toBe(partCount - 1); + }, + ); + it("completes multipart uploads with parts ordered by part number", async () => { const fetchMock = vi.fn( async (input: RequestInfo | URL, init?: RequestInit) => { diff --git a/packages/recorder-core/src/instant-mp4-uploader.ts b/packages/recorder-core/src/instant-mp4-uploader.ts index 11f33a33a68..164a219b97e 100644 --- a/packages/recorder-core/src/instant-mp4-uploader.ts +++ b/packages/recorder-core/src/instant-mp4-uploader.ts @@ -505,7 +505,14 @@ export class InstantRecordingUploader { ? DRIVE_PART_SIZE_BYTES : MIN_PART_SIZE_BYTES; while (this.bufferedBytes > 0) { - if (!force && this.bufferedBytes < targetPartSize) return; + if ( + !force && + (this.bufferedBytes < targetPartSize || + (this.provider === "googleDrive" && + this.bufferedBytes === targetPartSize)) + ) { + return; + } const partSize = Math.min(this.bufferedBytes, targetPartSize); const { part, remainingChunks, remainingBytes } =