Merge pull request #31 from trypostit/fix/x-chunked-upload-413

fix(x): drop chunked upload chunk size from 5MB to 1MB
This commit is contained in:
Paulo Castellano 2026-05-12 20:03:49 -03:00 committed by GitHub
commit 1bc1ec9ef5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 3 deletions

View file

@ -47,6 +47,15 @@ public static function fromApiResponse(mixed $response): static
);
}
if ($statusCode === 413) {
return new static(
userMessage: 'Media chunk rejected by X (payload too large).',
category: ErrorCategory::MediaFormat,
platformErrorCode: (string) $statusCode,
rawResponse: $rawResponse,
);
}
if (in_array($statusCode, [500, 502, 503, 504], true)) {
return new static(
userMessage: 'X server error. Please try again later.',

View file

@ -205,8 +205,11 @@ private function chunkedUpload(string $tempFile, int $totalBytes, string $mimeTy
throw new \Exception('No media_id returned from INIT');
}
// APPEND - Read from temp file in 5MB chunks (memory-safe)
$chunkSize = 5 * 1024 * 1024;
// APPEND - Read from temp file in 1MB chunks. Matches the
// twitter-api-v2 SDK default and X's own quickstart examples;
// larger chunks (we previously used 5MB) trigger 413 at the X
// edge with an empty body, surfacing as "An unknown X error".
$chunkSize = 1024 * 1024;
$handle = fopen($tempFile, 'r');
$index = 0;
@ -220,7 +223,7 @@ private function chunkedUpload(string $tempFile, int $totalBytes, string $mimeTy
$appendResponse = $this->socialHttp()->withToken($this->accessToken)
->timeout(300)
->attach('media', $chunk, 'chunk'.$index)
->attach('media', $chunk, 'chunk'.$index, ['Content-Type' => $mimeType])
->post("{$this->baseUrl}/media/upload/{$mediaId}/append", [
'segment_index' => $index,
]);

View file

@ -77,6 +77,18 @@
->and($exception->userMessage)->toBe('X server error. Please try again later.');
});
test('HTTP 413 with empty body maps to MediaFormat category', function () {
$response = Http::response('', 413);
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
$exception = XPublishException::fromApiResponse($fakeResponse);
expect($exception->category)->toBe(ErrorCategory::MediaFormat)
->and($exception->userMessage)->toBe('Media chunk rejected by X (payload too large).')
->and($exception->platformErrorCode)->toBe('413');
});
test('unknown type maps to Unknown category with detail as message', function () {
$response = Http::response([
'type' => 'https://api.x.com/2/problems/some-unknown-problem',