wayBetter for X
This commit is contained in:
commit
434f193217
720 changed files with 147465 additions and 0 deletions
161
apps/desktop/tests/hubs.test.ts
Normal file
161
apps/desktop/tests/hubs.test.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import { test } from 'node:test';
|
||||
import { addGate, addRewrite, addTap } from '../src/inject/netHub.ts';
|
||||
import { watchDom } from '../src/inject/domHub.ts';
|
||||
import { installResponseObserver, type PageObservation } from '../src/observer/network.ts';
|
||||
|
||||
const tick = () => new Promise(resolve => setTimeout(resolve, 15));
|
||||
const json = (body: unknown) => new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/json' } });
|
||||
|
||||
function countClones() {
|
||||
const original = Response.prototype.clone;
|
||||
let clones = 0;
|
||||
Response.prototype.clone = function (this: Response) { clones++; return original.call(this); };
|
||||
return { get count() { return clones; }, restore() { Response.prototype.clone = original; } };
|
||||
}
|
||||
|
||||
// ---------- shared request hook ----------
|
||||
|
||||
test('net hub: non-API requests skip every stage and get the inner promise itself', async () => {
|
||||
const promise = Promise.resolve(json({}));
|
||||
const target = { fetch: (() => promise) as unknown as typeof fetch };
|
||||
const seen: string[] = [];
|
||||
const offTap = addTap(target, { match: url => { seen.push(url); return true; }, maxBytes: 1e6, onJson() {} });
|
||||
const offRewrite = addRewrite(target, () => { seen.push('rewrite'); return null; });
|
||||
for (const url of ['https://abs.twimg.com/responsive-web/client-web/x.wasm', 'https://pbs.twimg.com/media/a.jpg', 'https://video.twimg.com/a.mp4', '/sw.js']) {
|
||||
assert.equal(target.fetch(url), promise, url);
|
||||
}
|
||||
assert.deepEqual(seen, [], 'no URL parsing, matching or cloning for non-API traffic');
|
||||
offTap();
|
||||
offRewrite();
|
||||
});
|
||||
|
||||
test('net hub: one clone and one parse feed every matching tap', async () => {
|
||||
const clones = countClones();
|
||||
try {
|
||||
const body = { data: { ok: true } };
|
||||
const target = { fetch: (async () => json(body)) as typeof fetch };
|
||||
const got: unknown[] = [];
|
||||
const a = addTap(target, { match: url => url.includes('HomeTimeline'), maxBytes: 1e6, onJson: value => got.push(value) });
|
||||
const b = addTap(target, { match: url => url.includes('Timeline'), maxBytes: 1e6, onJson: value => got.push(value) });
|
||||
const c = addTap(target, { match: () => false, maxBytes: 1e6, onJson: value => got.push(value) });
|
||||
const response = await target.fetch('https://x.com/i/api/graphql/q/HomeTimeline?variables=%7B%7D');
|
||||
assert.deepEqual(await response.json(), body, 'X still reads the original body');
|
||||
await tick();
|
||||
assert.equal(clones.count, 1);
|
||||
assert.equal(got.length, 2);
|
||||
assert.equal(got[0], got[1], 'both taps get the same parsed object');
|
||||
a(); b(); c();
|
||||
} finally {
|
||||
clones.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('net hub: rewrite runs before the gate; unregistering the last stage restores fetch', async () => {
|
||||
const calls: unknown[] = [];
|
||||
const inner = ((input: unknown) => { calls.push(input); return Promise.resolve(json({})); }) as typeof fetch;
|
||||
const target = { fetch: inner };
|
||||
const order: string[] = [];
|
||||
const offRewrite = addRewrite(target, input => { order.push('rewrite'); return String(input).replace('A', 'B'); });
|
||||
const offGate = addGate(target, (input, _init, send) => { order.push(`gate:${String(input)}`); return String(input).endsWith('B') ? send() : null; });
|
||||
await target.fetch('/i/api/A');
|
||||
assert.deepEqual(order, ['rewrite', 'gate:/i/api/B']);
|
||||
assert.deepEqual(calls, ['/i/api/B']);
|
||||
offRewrite();
|
||||
assert.notEqual(target.fetch, inner, 'still hooked while the gate is registered');
|
||||
offGate();
|
||||
assert.equal(target.fetch, inner);
|
||||
});
|
||||
|
||||
// ---------- shared DOM watcher ----------
|
||||
|
||||
function fakeDomWindow(pathname = '/home') {
|
||||
const frames: FrameRequestCallback[] = [];
|
||||
const observers: { callback: MutationCallback; options?: MutationObserverInit; connected: boolean }[] = [];
|
||||
class FakeObserver {
|
||||
entry: { callback: MutationCallback; options?: MutationObserverInit; connected: boolean };
|
||||
constructor(callback: MutationCallback) { this.entry = { callback, connected: false }; observers.push(this.entry); }
|
||||
observe(_node: Node, options: MutationObserverInit) { this.entry.options = options; this.entry.connected = true; }
|
||||
disconnect() { this.entry.connected = false; }
|
||||
}
|
||||
const win = {
|
||||
document: { body: {}, addEventListener() {}, removeEventListener() {} } as unknown as Document,
|
||||
location: { pathname },
|
||||
MutationObserver: FakeObserver as unknown as typeof MutationObserver,
|
||||
requestAnimationFrame(callback: FrameRequestCallback) { frames.push(callback); return frames.length; },
|
||||
};
|
||||
const flush = () => { for (const frame of frames.splice(0)) frame(0); };
|
||||
const mutate = (...types: MutationRecordType[]) => observers[0]!.callback(types.map(type => ({ type, attributeName: type === 'attributes' ? 'aria-selected' : null })) as unknown as MutationRecord[], {} as MutationObserver);
|
||||
return { win, frames, observers, flush, mutate };
|
||||
}
|
||||
|
||||
test('dom hub: one observer and one frame for all features; kinds and phases respected', () => {
|
||||
const { win, frames, observers, flush, mutate } = fakeDomWindow();
|
||||
const runs: string[] = [];
|
||||
const writer = watchDom(win, () => runs.push('writer'));
|
||||
const text = watchDom(win, () => runs.push('text'), { characterData: true });
|
||||
const reader = watchDom(win, () => runs.push('reader'), { phase: 'read' });
|
||||
const tabs = watchDom(win, () => runs.push('tabs'), { attributes: ['aria-selected'] });
|
||||
assert.equal(observers.length, 1);
|
||||
assert.deepEqual(observers[0]!.options, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['aria-selected'] });
|
||||
mutate('childList'); mutate('childList'); mutate('characterData');
|
||||
assert.equal(frames.length, 1, 'many mutation callbacks, one frame');
|
||||
flush();
|
||||
assert.deepEqual(runs, ['reader', 'writer', 'text', 'tabs'], 'layout readers before DOM writers');
|
||||
runs.length = 0;
|
||||
mutate('characterData');
|
||||
flush();
|
||||
assert.deepEqual(runs, ['text'], 'text changes only wake features that asked for them');
|
||||
runs.length = 0;
|
||||
mutate('attributes');
|
||||
flush();
|
||||
assert.deepEqual(runs, ['tabs']);
|
||||
runs.length = 0;
|
||||
writer.schedule(); writer.schedule();
|
||||
flush();
|
||||
assert.deepEqual(runs, ['writer'], 'explicit schedule runs once per frame');
|
||||
for (const handle of [writer, text, reader, tabs]) handle.dispose();
|
||||
assert.equal(observers[0]!.connected, false, 'last feature off disconnects the observer');
|
||||
});
|
||||
|
||||
test('dom hub: chat pages skip features marked offOnChat', () => {
|
||||
const { win, flush, mutate } = fakeDomWindow('/i/chat/123-456');
|
||||
const runs: string[] = [];
|
||||
const media = watchDom(win, () => runs.push('media'), { offOnChat: true });
|
||||
const nav = watchDom(win, () => runs.push('nav'));
|
||||
mutate('childList');
|
||||
flush();
|
||||
assert.deepEqual(runs, ['nav']);
|
||||
win.location.pathname = '/home';
|
||||
mutate('childList');
|
||||
flush();
|
||||
assert.deepEqual(runs, ['nav', 'media', 'nav']);
|
||||
media.dispose();
|
||||
nav.dispose();
|
||||
});
|
||||
|
||||
// ---------- response observer on chat pages ----------
|
||||
|
||||
test('response observer reads nothing on chat and DM pages', async () => {
|
||||
const clones = countClones();
|
||||
try {
|
||||
const body = { data: { user: { result: { __typename: 'User', rest_id: '1', legacy: { following: true } } } } };
|
||||
for (const pathname of ['/i/chat/1-2', '/messages', '/home']) {
|
||||
const page = Object.assign(new EventTarget(), {
|
||||
fetch: (async () => json(body)) as typeof fetch, CustomEvent,
|
||||
location: { origin: 'https://x.com', href: `https://x.com${pathname}`, pathname },
|
||||
top: null as unknown,
|
||||
});
|
||||
page.top = page;
|
||||
const events: PageObservation[] = [];
|
||||
const handle = installResponseObserver(page as unknown as Window & typeof globalThis, { onObservation: event => events.push(event) });
|
||||
await (await page.fetch('https://x.com/i/api/graphql/q/UserByScreenName')).text();
|
||||
await tick();
|
||||
assert.equal(events.length, pathname === '/home' ? 1 : 0, pathname);
|
||||
handle.dispose();
|
||||
}
|
||||
assert.equal(clones.count, 1, 'only the /home response was cloned');
|
||||
} finally {
|
||||
clones.restore();
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue