DOM automation for Google Labs Flow — background worker injects trusted input via the DevTools Protocol (Flow's editor ignores synthetic events), polls a local queue, harvests generated asset URLs. Includes the generated game+promo prompt bank. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 lines
2.4 KiB
JavaScript
58 lines
2.4 KiB
JavaScript
// Background service worker: performs TRUSTED input via the Chrome DevTools Protocol.
|
|
// Flow's agent editor ignores synthetic DOM events (isTrusted=false) — the text shows
|
|
// but the app's state stays empty ("no prompt" on send). CDP Input.* events are trusted,
|
|
// so the framework actually registers them. Requires the "debugger" permission; the user
|
|
// sees a "started debugging this browser" banner while it runs (expected).
|
|
|
|
const attached = new Set();
|
|
|
|
function attach(tabId) {
|
|
return new Promise((res, rej) =>
|
|
chrome.debugger.attach({ tabId }, '1.3', () =>
|
|
chrome.runtime.lastError ? rej(chrome.runtime.lastError) : res()));
|
|
}
|
|
function cmd(tabId, method, params) {
|
|
return new Promise((res, rej) =>
|
|
chrome.debugger.sendCommand({ tabId }, method, params || {}, (r) =>
|
|
chrome.runtime.lastError ? rej(chrome.runtime.lastError) : res(r)));
|
|
}
|
|
async function ensure(tabId) {
|
|
if (attached.has(tabId)) return;
|
|
try { await attach(tabId); attached.add(tabId); }
|
|
catch (e) {
|
|
// "Another debugger is already attached" => DevTools open on the tab; treat as attached-ish
|
|
if (/already attached/i.test(String(e.message || e))) attached.add(tabId);
|
|
else throw e;
|
|
}
|
|
}
|
|
|
|
const ENTER = { key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13 };
|
|
|
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
const tabId = sender.tab && sender.tab.id;
|
|
(async () => {
|
|
try {
|
|
if (!tabId) throw new Error('no tab id');
|
|
await ensure(tabId);
|
|
if (msg.type === 'insertText') {
|
|
await cmd(tabId, 'Input.insertText', { text: msg.text });
|
|
sendResponse({ ok: true });
|
|
} else if (msg.type === 'pressEnter') {
|
|
await cmd(tabId, 'Input.dispatchKeyEvent', { type: 'keyDown', ...ENTER });
|
|
await cmd(tabId, 'Input.dispatchKeyEvent', { type: 'keyUp', ...ENTER });
|
|
sendResponse({ ok: true });
|
|
} else if (msg.type === 'detach') {
|
|
if (attached.has(tabId)) { chrome.debugger.detach({ tabId }, () => {}); attached.delete(tabId); }
|
|
sendResponse({ ok: true });
|
|
} else {
|
|
sendResponse({ ok: false, error: 'unknown message' });
|
|
}
|
|
} catch (e) {
|
|
sendResponse({ ok: false, error: String(e.message || e) });
|
|
}
|
|
})();
|
|
return true; // keep the message channel open for the async response
|
|
});
|
|
|
|
chrome.debugger.onDetach.addListener((src) => { if (src.tabId) attached.delete(src.tabId); });
|