import assert from 'node:assert/strict'; import { test } from 'node:test'; import { configureRuntime, isSiteUrl, type SiteAdapter } from '../src/site.ts'; import { addGate, addRewrite, addTap, addXhrGate, fetchHooked, isApiUrl } from '../src/netHub.ts'; import { isHeavyPath, watchDom } from '../src/domHub.ts'; const SITE: SiteAdapter = { product: 'wayBetter for Test', hosts: ['site.test'], runtimeGlobal: 'Demo', heavyPath: /^\/chat(?:\/|$)/, apiMarks: ['/api/'], xhrMarks: ['/upload'], plugins: { name: 'Demo', product: 'wayBetter for Test', storagePrefix: 'demo-plugin:', styleDataKey: 'demoPlugin', toastId: 'demo-toast', toastBackground: '#000', itemSelector: 'article', chordKeys: [] }, }; configureRuntime(SITE); 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; } }; } test('site adapter: origin and API marks come from the adapter', () => { assert.ok(isSiteUrl(SITE, 'https://site.test/a')); for (const url of ['http://site.test/', 'https://site.test.evil/', 'https://u:p@site.test/', 'nope']) assert.equal(isSiteUrl(SITE, url), false, url); assert.ok(isApiUrl('https://site.test/api/v1/feed')); assert.equal(isApiUrl('https://cdn.site.test/a.png'), false); assert.ok(isHeavyPath('/chat/1') && !isHeavyPath('/home')); }); 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://cdn.site.test/app.wasm', 'https://cdn.site.test/a.jpg', '/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('Feed'), maxBytes: 1e6, onJson: value => got.push(value) }); const b = addTap(target, { match: url => url.includes('HomeFeed'), 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://site.test/api/HomeFeed?cursor=1'); assert.deepEqual(await response.json(), body, 'the page 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: oversized bodies are never cloned', async () => { const clones = countClones(); try { const response = new Response('{}', { status: 200, headers: { 'content-length': '5000000' } }); const target = { fetch: (async () => response) as typeof fetch }; const off = addTap(target, { match: () => true, maxBytes: 1000, onJson() { assert.fail('not delivered'); } }); await target.fetch('/api/big'); await tick(); assert.equal(clones.count, 0); off(); } 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; }); assert.ok(fetchHooked(target)); await target.fetch('/api/A'); assert.deepEqual(order, ['rewrite', 'gate:/api/B']); assert.deepEqual(calls, ['/api/B']); offRewrite(); assert.notEqual(target.fetch, inner, 'still hooked while the gate is registered'); offGate(); assert.equal(target.fetch, inner); assert.equal(fetchHooked(target), false); }); test('net hub: XHR gates see API and XHR-mark requests only, and can rewrite the URL', () => { const sent: unknown[] = []; class FakeXhr { url = ''; open(_method: string, url: string) { this.url = url; } send(body?: unknown) { sent.push([this.url, body]); } addEventListener() {} } const target = { fetch: (async () => json({})) as typeof fetch, XMLHttpRequest: FakeXhr as unknown as typeof XMLHttpRequest }; const seen: string[] = []; const off = addXhrGate(target, { open(_xhr, request) { return request.url.includes('/api/old') ? request.url.replace('old', 'new') : undefined; }, send(_xhr, request, body, sendNative) { seen.push(request.url); sendNative(`${String(body)}!`); return true; }, }); for (const url of ['/api/old', '/upload/chunk', '/static/app.js']) { const xhr = new FakeXhr(); xhr.open('post', url); xhr.send('body'); } assert.deepEqual(seen, ['/api/new', '/upload/chunk']); assert.deepEqual(sent, [['/api/new', 'body!'], ['/upload/chunk', 'body!'], ['/static/app.js', 'body']]); off(); assert.equal(FakeXhr.prototype.send.name, 'send', 'the last gate restores the prototype'); }); 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: heavy pages skip features marked offOnHeavy', () => { const { win, flush, mutate } = fakeDomWindow('/chat/123'); const runs: string[] = []; const media = watchDom(win, () => runs.push('media'), { offOnHeavy: 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(); }); test('dom hub: a failing feature does not stop the others', () => { const { win, flush, mutate } = fakeDomWindow(); const runs: string[] = []; const errors: unknown[] = []; const original = console.error; console.error = (...args: unknown[]) => { errors.push(args[0]); }; try { const bad = watchDom(win, () => { throw new Error('boom'); }); const good = watchDom(win, () => runs.push('good')); mutate('childList'); flush(); assert.deepEqual(runs, ['good']); assert.deepEqual(errors, ['[wayBetter for Test] DOM feature failed']); bad.dispose(); good.dispose(); } finally { console.error = original; } });