// Fixes for docs/X-BUG-BACKLOG.md: "See new posts" jump, silent media upload failures, Following order. import assert from 'node:assert/strict'; import { test } from 'node:test'; import { chronologicalVariables, installFollowingChronological, rewriteFollowingBody, rewriteFollowingUrl } from '../src/fixes/followingOrder.ts'; import { MAX_UPLOAD_RETRIES, classifyUpload, installUploadRetry, parseUpload, processingFailure, shouldRetryUpload, uploadFailureMessage, uploadRetryDelay, } from '../src/fixes/uploadRetry.ts'; import { isNewPostsShortcut, restoreStep } from '../src/fixes/timeline.ts'; const BASE = 'https://x.com/home'; const LATEST = '/i/api/graphql/OQPHTgwczzp9RMAPt6BH9A/HomeLatestTimeline'; const UPLOAD = 'https://upload.x.com/i/media/upload.json'; // ---------- fake XHR (records what reaches the wire, replies on demand) ---------- type Reply = { status: number } | 'error' | 'timeout'; function xhrEnv() { const wire: { method: string; url: string; body: unknown; headers: [string, string][]; xhr: FakeXHR }[] = []; class FakeXHR extends EventTarget { #s = { readyState: 0, status: 0, text: '', method: '', url: '', headers: [] as [string, string][] }; responseType: XMLHttpRequestResponseType = ''; timeout = 0; withCredentials = false; get readyState() { return this.#s.readyState; } get status() { return this.#s.status; } get responseText() { return this.#s.text; } open(method: string, url: string) { Object.assign(this.#s, { method, url, readyState: 1, status: 0, text: '', headers: [] }); this.dispatchEvent(new Event('readystatechange')); } setRequestHeader(name: string, value: string) { this.#s.headers.push([name, value]); } send(body: unknown) { wire.push({ method: this.#s.method, url: this.#s.url, body, headers: [...this.#s.headers], xhr: this }); } reply(reply: Reply, text = '{}') { const failed = typeof reply === 'string'; Object.assign(this.#s, { readyState: 4, status: failed ? 0 : reply.status, text: failed ? '' : text }); for (const type of ['readystatechange', failed ? reply : 'load', 'loadend']) this.dispatchEvent(new Event(type)); } abort() { if (this.#s.readyState === 4 || this.#s.readyState === 0) return; Object.assign(this.#s, { readyState: 4, status: 0 }); for (const type of ['readystatechange', 'abort', 'loadend']) this.dispatchEvent(new Event(type)); this.#s.readyState = 0; } } const timers: (() => void)[] = []; const delays: number[] = []; const win = { fetch: (async () => new Response('{}')) as typeof fetch, location: { href: BASE }, XMLHttpRequest: FakeXHR as unknown as typeof XMLHttpRequest, setTimeout: (run: () => void, ms: number) => { delays.push(ms); timers.push(run); return timers.length; }, clearTimeout: (id: number) => { timers[id - 1] = () => {}; }, }; const runTimers = () => { while (timers.some(Boolean)) { const index = timers.findIndex(Boolean); const run = timers[index]!; timers[index] = undefined as never; run(); } }; return { win, wire, delays, runTimers, FakeXHR }; } /** X's uploader: open, on* handlers after open, headers, send; records what X's handlers saw. */ function xUpload(win: { XMLHttpRequest: typeof XMLHttpRequest }, command: string, extra = '', body: unknown = 'chunk') { const xhr = new win.XMLHttpRequest(); const seen: string[] = []; xhr.open(command === 'STATUS' ? 'GET' : 'POST', `${UPLOAD}?command=${command}&media_id=77${extra}`, true); for (const type of ['load', 'error', 'timeout', 'abort', 'loadend']) xhr.addEventListener(type, () => seen.push(type === 'load' ? `load ${xhr.status}` : type)); xhr.addEventListener('readystatechange', () => { if (xhr.readyState === 4) seen.push('rs4'); }); xhr.setRequestHeader('authorization', 'Bearer test'); xhr.setRequestHeader('x-csrf-token', 'csrf'); xhr.send(body as XMLHttpRequestBodyInit); return { xhr, seen }; } // ---------- Following in time order ---------- test('chronologicalVariables turns only enableRanking: true off', () => { assert.deepEqual(chronologicalVariables({ count: 40, enableRanking: true, seenTweetIds: ['1'] }), { count: 40, enableRanking: false, seenTweetIds: ['1'] }); for (const value of [{ enableRanking: false }, { count: 20 }, { enableRanking: 'true' }, null, [], 'x']) { assert.equal(chronologicalVariables(value), null, JSON.stringify(value)); } }); test('Following rewrite: GET query and POST body of HomeLatestTimeline, nothing else', () => { const get = `${LATEST}?variables=${encodeURIComponent(JSON.stringify({ count: 20, enableRanking: true, requestContext: 'launch' }))}&features=%7B%7D`; const next = rewriteFollowingUrl(get, BASE)!; assert.ok(next.startsWith(LATEST), 'relative stays relative'); const params = new URL(next, BASE).searchParams; assert.deepEqual(JSON.parse(params.get('variables')!), { count: 20, enableRanking: false, requestContext: 'launch' }); assert.equal(params.get('features'), '{}', 'other parameters kept'); assert.match(rewriteFollowingUrl(`https://x.com${get}`, BASE)!, /^https:\/\/x\.com\//); const body = JSON.stringify({ variables: { count: 40, cursor: 'c', enableRanking: true, seenTweetIds: ['9'] }, features: { a: true }, queryId: 'q' }); assert.deepEqual(JSON.parse(rewriteFollowingBody(`https://x.com${LATEST}`, BASE, body)!), { variables: { count: 40, cursor: 'c', enableRanking: false, seenTweetIds: ['9'] }, features: { a: true }, queryId: 'q' }); const recent = JSON.stringify({ variables: { enableRanking: false } }); assert.equal(rewriteFollowingBody(LATEST, BASE, recent), null, 'X\'s own "Recent" sort untouched'); assert.equal(rewriteFollowingBody('/i/api/graphql/q/HomeTimeline', BASE, body), null, 'For you untouched'); assert.equal(rewriteFollowingBody(`https://api.example${LATEST}`, BASE, body), null, 'other origins untouched'); assert.equal(rewriteFollowingBody(LATEST, BASE, '{bad'), null); assert.equal(rewriteFollowingBody(LATEST, BASE, new Blob([body])), null); assert.equal(rewriteFollowingUrl(`${LATEST}?variables=%7Bbad`, BASE), null); assert.equal(rewriteFollowingUrl('/i/api/graphql/q/HomeTimeline?variables=%7B%22enableRanking%22%3Atrue%7D', BASE), null); }); test('Following rewrite on X\'s XHR: POST body and GET URL rewritten, others untouched, dispose restores', () => { const env = xhrEnv(); const nativeOpen = env.FakeXHR.prototype.open; const nativeSend = env.FakeXHR.prototype.send; const handle = installFollowingChronological(env.win); const post = new env.win.XMLHttpRequest(); post.open('POST', `https://x.com${LATEST}`); post.send(JSON.stringify({ variables: { enableRanking: true, count: 40 }, queryId: 'q' })); assert.equal(JSON.parse(String(env.wire[0]!.body)).variables.enableRanking, false); assert.equal(env.wire[0]!.xhr, post, 'sent on X\'s own object'); const get = new env.win.XMLHttpRequest(); get.open('GET', `https://x.com${LATEST}?variables=${encodeURIComponent('{"enableRanking":true}')}`); get.send(null); assert.deepEqual(JSON.parse(new URL(env.wire[1]!.url).searchParams.get('variables')!), { enableRanking: false }); const other = new env.win.XMLHttpRequest(); const otherBody = JSON.stringify({ variables: { enableRanking: true } }); other.open('POST', 'https://x.com/i/api/graphql/q/HomeTimeline'); other.send(otherBody); assert.equal(env.wire[2]!.body, otherBody); handle.dispose(); assert.equal(env.FakeXHR.prototype.open, nativeOpen); assert.equal(env.FakeXHR.prototype.send, nativeSend); }); // ---------- media upload retry ---------- test('parseUpload: X\'s chunked upload commands on upload.x.com only', () => { assert.deepEqual(parseUpload(`${UPLOAD}?command=APPEND&media_id=77&segment_index=3`), { command: 'APPEND', mediaId: '77', segment: '3' }); assert.deepEqual(parseUpload('https://upload.twitter.com/i/media/upload.json?command=STATUS&media_id=77'), { command: 'STATUS', mediaId: '77', segment: null }); assert.equal(parseUpload(`${UPLOAD}?command=INIT&total_bytes=1`)?.command, 'INIT'); for (const url of [ `${UPLOAD}?command=DELETE`, UPLOAD, 'https://evil.example/i/media/upload.json?command=APPEND', 'http://upload.x.com/i/media/upload.json?command=APPEND', 'https://x.com/i/api/graphql/q/CreateTweet', 'not a url', ]) assert.equal(parseUpload(url), null, url); }); test('retry policy: only APPEND/STATUS, only failures X gives up on, at most 3 times with backoff', () => { const append = parseUpload(`${UPLOAD}?command=APPEND&media_id=1&segment_index=0`)!; const status = parseUpload(`${UPLOAD}?command=STATUS&media_id=1`)!; const init = parseUpload(`${UPLOAD}?command=INIT`)!; const finalize = parseUpload(`${UPLOAD}?command=FINALIZE&media_id=1`)!; for (const outcome of [{ kind: 'error' }, { kind: 'timeout' }, { kind: 'load', status: 503 }, { kind: 'load', status: 0 }] as const) { assert.equal(classifyUpload(outcome), 'fatal'); assert.equal(shouldRetryUpload(append, outcome, 0), true); assert.equal(shouldRetryUpload(status, outcome, 2), true); assert.equal(shouldRetryUpload(append, outcome, MAX_UPLOAD_RETRIES), false, 'bounded'); assert.equal(shouldRetryUpload(init, outcome, 0), false, 'INIT never repeated'); assert.equal(shouldRetryUpload(finalize, outcome, 0), false, 'FINALIZE never repeated'); } for (const outcome of [{ kind: 'load', status: 200 }, { kind: 'load', status: 204 }, { kind: 'load', status: 400 }, { kind: 'load', status: 500 }, { kind: 'abort' }] as const) { assert.equal(shouldRetryUpload(append, outcome, 0), false, JSON.stringify(outcome)); } assert.equal(classifyUpload({ kind: 'load', status: 500 }), 'failed', 'X retries this one itself'); assert.equal(classifyUpload({ kind: 'abort' }), 'aborted'); assert.deepEqual([0, 1, 2].map(uploadRetryDelay), [1000, 2000, 4000]); assert.equal(MAX_UPLOAD_RETRIES, 3); }); test('processingFailure and failure messages', () => { assert.match(processingFailure('{"media_id":1,"processing_info":{"state":"failed","error":{"code":1,"name":"InvalidMedia","message":"Unsupported video format"}}}')!, /Unsupported video format/); assert.match(processingFailure('{"processing_info":{"state":"failed"}}')!, /could not process/); for (const text of ['{"processing_info":{"state":"succeeded"}}', '{"processing_info":{"state":"in_progress"}}', '{}', '{bad', 'null']) assert.equal(processingFailure(text), null, text); const append = parseUpload(`${UPLOAD}?command=APPEND&media_id=1&segment_index=0`)!; assert.match(uploadFailureMessage(append, { kind: 'load', status: 503 }, 3), /APPEND, HTTP 503 after 3 retries/); assert.match(uploadFailureMessage(append, { kind: 'timeout' }, 0), /APPEND, timed out\)/); assert.match(uploadFailureMessage(append, { kind: 'error' }, 1), /network error after 1 retry/); }); test('upload retry: a failed APPEND is re-sent on X\'s own XHR; X only sees the final success', () => { const env = xhrEnv(); const notes: string[] = []; const handle = installUploadRetry(env.win, message => notes.push(message)); const form = { chunk: 'bytes' }; const { xhr, seen } = xUpload(env.win, 'APPEND', '&segment_index=2', form); assert.equal(env.wire.length, 1); (env.wire[0]!.xhr).reply('error'); assert.deepEqual(seen, [], 'failed attempt hidden from X'); env.runTimers(); assert.equal(env.wire.length, 2, 'one retry'); (env.wire[1]!.xhr).reply({ status: 503 }); env.runTimers(); (env.wire[2]!.xhr).reply('timeout'); env.runTimers(); (env.wire[3]!.xhr).reply({ status: 204 }, ''); assert.deepEqual(env.delays, [1000, 2000, 4000]); for (const sent of env.wire) { assert.equal(sent.xhr, xhr, 'same XHR object'); assert.equal(sent.body, form, 'same body'); assert.equal(sent.url, env.wire[0]!.url, 'same request'); assert.deepEqual(sent.headers, [['authorization', 'Bearer test'], ['x-csrf-token', 'csrf']], 'headers re-applied once'); } assert.deepEqual(seen, ['rs4', 'load 204', 'loadend']); assert.deepEqual(notes, []); handle.dispose(); }); test('upload retry: gives up after 3 retries, X sees the failure and a toast explains it', () => { const env = xhrEnv(); const notes: string[] = []; installUploadRetry(env.win, message => notes.push(message)); const { seen } = xUpload(env.win, 'STATUS'); for (let attempt = 0; attempt <= MAX_UPLOAD_RETRIES; attempt++) { env.wire[attempt]!.xhr.reply({ status: 503 }); env.runTimers(); } assert.equal(env.wire.length, MAX_UPLOAD_RETRIES + 1); assert.deepEqual(seen, ['rs4', 'load 503', 'loadend']); assert.equal(notes.length, 1); assert.match(notes[0]!, /STATUS, HTTP 503 after 3 retries/); }); test('upload retry: INIT/FINALIZE and ordinary HTTP errors pass straight through', () => { const env = xhrEnv(); const notes: string[] = []; installUploadRetry(env.win, message => notes.push(message)); const init = xUpload(env.win, 'INIT'); env.wire[0]!.xhr.reply('error'); env.runTimers(); assert.equal(env.wire.length, 1, 'INIT not repeated'); assert.deepEqual(init.seen, ['rs4', 'error', 'loadend']); assert.match(notes[0]!, /INIT, network error\)/); const bad = xUpload(env.win, 'APPEND', '&segment_index=0'); env.wire[1]!.xhr.reply({ status: 400 }, '{"errors":[]}'); env.runTimers(); assert.equal(env.wire.length, 2, 'X\'s own retry policy handles this one'); assert.deepEqual(bad.seen, ['rs4', 'load 400', 'loadend']); assert.equal(notes.length, 1); const finalize = xUpload(env.win, 'FINALIZE'); env.wire[2]!.xhr.reply({ status: 200 }, '{"processing_info":{"state":"failed","error":{"message":"Video too long"}}}'); assert.deepEqual(finalize.seen, ['rs4', 'load 200', 'loadend']); assert.match(notes[1]!, /Video too long/); }); test('upload retry: X cancelling during the backoff gets abort/loadend and nothing is re-sent', () => { const env = xhrEnv(); const notes: string[] = []; installUploadRetry(env.win, message => notes.push(message)); const { xhr, seen } = xUpload(env.win, 'APPEND', '&segment_index=0'); env.wire[0]!.xhr.reply('error'); xhr.abort(); env.runTimers(); assert.equal(env.wire.length, 1); assert.deepEqual(seen, ['abort', 'loadend']); assert.deepEqual(notes, []); const second = xUpload(env.win, 'APPEND', '&segment_index=1'); second.xhr.abort(); env.runTimers(); assert.equal(env.wire.length, 2); assert.deepEqual(second.seen, ['rs4', 'abort', 'loadend'], 'in-flight abort is native'); }); test('upload retry: non-upload XHRs are untouched and dispose unhooks', () => { const env = xhrEnv(); const nativeSend = env.FakeXHR.prototype.send; const handle = installUploadRetry(env.win, () => {}); const xhr = new env.win.XMLHttpRequest(); xhr.open('POST', 'https://x.com/i/api/graphql/q/CreateTweet'); xhr.send('{}'); assert.equal(Object.getOwnPropertyNames(xhr).includes('abort'), false, 'no per-object overrides'); handle.dispose(); assert.equal(env.FakeXHR.prototype.send, nativeSend); }); // ---------- "See new posts" ---------- test('"." shortcut counts only without modifiers and outside text fields', () => { const none = { ctrl: false, alt: false, meta: false }; assert.equal(isNewPostsShortcut('.', none, false), true); assert.equal(isNewPostsShortcut('.', none, true), false); assert.equal(isNewPostsShortcut('.', { ...none, ctrl: true }, false), false); assert.equal(isNewPostsShortcut(',', none, false), false); }); test('pill restore: X jumped to the top, the post is brought back, then held while posts insert above', () => { const state = { fallback: true, jumped: false, stable: 0 }; // Right after X's scrollTo(0, 0): the post is still rendered, 3000px lower than before. assert.deepEqual(restoreStep(state, { top: 3120, targetTop: 120, scrollY: 0, targetScrollY: 3000, viewport: 800 }), { kind: 'scrollBy', dy: 3000 }); // Virtualized away: jump near the old offset once, then look again. assert.deepEqual(restoreStep(state, { top: null, targetTop: 120, scrollY: 0, targetScrollY: 3000, viewport: 800 }), { kind: 'scrollTo', y: 3000 }); // New posts inserted above push it down by their height: compensated. assert.deepEqual(restoreStep({ ...state, jumped: true }, { top: 1020, targetTop: 120, scrollY: 3000, targetScrollY: 3000, viewport: 800 }), { kind: 'scrollBy', dy: 900 }); assert.deepEqual(restoreStep({ ...state, stable: 2 }, { top: 121, targetTop: 120, scrollY: 3900, targetScrollY: 3000, viewport: 800 }), { kind: 'done' }); });