mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-09-02 14:07:55 +00:00
147 lines
5.8 KiB
JavaScript
147 lines
5.8 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import vm from 'node:vm';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const backgroundSource = fs.readFileSync(path.join(extensionDir, 'background.js'), 'utf8');
|
|
const contentSource = fs.readFileSync(path.join(extensionDir, 'content.js'), 'utf8');
|
|
const providerSource = fs.readFileSync(path.join(extensionDir, 'page-api-seek-overrides.js'), 'utf8');
|
|
|
|
function extractFunction(source, name) {
|
|
const start = source.indexOf(`function ${name}(`);
|
|
expect(start).toBeGreaterThan(-1);
|
|
const bodyStart = source.indexOf('{', start);
|
|
let depth = 0;
|
|
for (let index = bodyStart; index < source.length; index++) {
|
|
if (source[index] === '{') depth++;
|
|
if (source[index] === '}') depth--;
|
|
if (depth === 0) return source.slice(start, index + 1);
|
|
}
|
|
throw new Error(`${name} body did not terminate`);
|
|
}
|
|
|
|
function createBridgeHarness({ sessionIds = ['watch-main'], players = {} } = {}) {
|
|
const listeners = new Set();
|
|
const posted = [];
|
|
const fakeWindow = {
|
|
location: { hostname: 'www.netflix.com' },
|
|
koalaFindPageApiSeekProvider: () => ({
|
|
provider: 'netflix',
|
|
actions: ['play', 'pause', 'seek']
|
|
}),
|
|
netflix: {
|
|
appContext: {
|
|
state: {
|
|
playerApp: {
|
|
getAPI: () => ({
|
|
videoPlayer: {
|
|
getAllPlayerSessionIds: () => sessionIds,
|
|
getVideoPlayerBySessionId: id => players[id] || null
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
},
|
|
addEventListener(type, listener) {
|
|
if (type === 'message') listeners.add(listener);
|
|
},
|
|
removeEventListener(type, listener) {
|
|
if (type === 'message') listeners.delete(listener);
|
|
},
|
|
postMessage(data) {
|
|
posted.push(data);
|
|
}
|
|
};
|
|
const installSource = extractFunction(backgroundSource, 'installPageApiPlayerBridge');
|
|
const install = Function(
|
|
'window',
|
|
'document',
|
|
'setInterval',
|
|
'clearInterval',
|
|
`'use strict'; ${installSource}; return installPageApiPlayerBridge;`
|
|
)(
|
|
fakeWindow,
|
|
{ querySelector: () => null },
|
|
() => 1,
|
|
() => {}
|
|
);
|
|
install();
|
|
return {
|
|
fakeWindow,
|
|
posted,
|
|
dispatch(data) {
|
|
for (const listener of [...listeners]) listener({ source: fakeWindow, data });
|
|
}
|
|
};
|
|
}
|
|
|
|
async function sendCommand(harness, action, time = null) {
|
|
const requestId = `request-${action}`;
|
|
harness.dispatch({
|
|
__koalaPageApiPlayer: 1,
|
|
kind: 'command',
|
|
requestId,
|
|
action,
|
|
time
|
|
});
|
|
await vi.waitFor(() => {
|
|
expect(harness.posted.some(message => message.kind === 'result' && message.requestId === requestId)).toBe(true);
|
|
});
|
|
return harness.posted.find(message => message.kind === 'result' && message.requestId === requestId);
|
|
}
|
|
|
|
describe('page API player bridge', () => {
|
|
it('declares every Netflix write as bridge-only while Disney remains seek-only', () => {
|
|
const root = {};
|
|
vm.runInNewContext(providerSource, { globalThis: root, URL });
|
|
expect(root.koalaFindPageApiSeekProvider('https://www.netflix.com/watch/1')).toMatchObject({
|
|
provider: 'netflix',
|
|
actions: ['play', 'pause', 'seek']
|
|
});
|
|
expect(root.koalaFindPageApiSeekProvider('www.disneyplus.com')).toMatchObject({
|
|
provider: 'disney',
|
|
actions: ['seek']
|
|
});
|
|
});
|
|
|
|
it('prefers the Netflix watch session and confirms play, pause, and seek', async () => {
|
|
const preview = { seek: vi.fn(), play: vi.fn(), pause: vi.fn() };
|
|
const player = { seek: vi.fn(), play: vi.fn(), pause: vi.fn() };
|
|
const harness = createBridgeHarness({
|
|
sessionIds: ['motion-billboard-1', 'watch-main'],
|
|
players: { 'motion-billboard-1': preview, 'watch-main': player }
|
|
});
|
|
|
|
await expect(sendCommand(harness, 'seek', 12.345)).resolves.toMatchObject({ ok: true });
|
|
await expect(sendCommand(harness, 'pause')).resolves.toMatchObject({ ok: true });
|
|
await expect(sendCommand(harness, 'play')).resolves.toMatchObject({ ok: true });
|
|
expect(player.seek).toHaveBeenCalledWith(12345);
|
|
expect(player.pause).toHaveBeenCalledOnce();
|
|
expect(player.play).toHaveBeenCalledOnce();
|
|
expect(preview.seek).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns an explicit failure when the Netflix player is unavailable', async () => {
|
|
const harness = createBridgeHarness({ sessionIds: ['watch-missing'], players: {} });
|
|
await expect(sendCommand(harness, 'seek', 10)).resolves.toMatchObject({
|
|
ok: false,
|
|
reason: 'player_unavailable'
|
|
});
|
|
});
|
|
|
|
it('keeps provider actions fail-closed and ACKs only applied remote commands', () => {
|
|
const seekVideo = extractFunction(contentSource, 'seekVideo');
|
|
const request = extractFunction(contentSource, 'requestPageApiAction');
|
|
const remote = extractFunction(contentSource, 'applyServerMediaAction');
|
|
expect(seekVideo.indexOf('pageApiActionRequired(EVENTS.SEEK)'))
|
|
.toBeLessThan(seekVideo.indexOf('video.currentTime ='));
|
|
expect(seekVideo).toContain('return requestPageApiAction(EVENTS.SEEK, targetTime)');
|
|
expect(request).toContain('refusing unsafe native fallback');
|
|
expect(remote.indexOf('if (!applied)')).toBeLessThan(remote.indexOf("type: 'CMD_ACK'"));
|
|
expect(backgroundSource).toContain('target: { tabId },\n world: \'MAIN\'');
|
|
});
|
|
});
|