Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions extension/dist/assets/protocol-Z52ThYIj.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const DAEMON_PORT = 19825;
const DAEMON_HOST = "localhost";
const DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
const DAEMON_PING_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}/ping`;
const WS_RECONNECT_BASE_DELAY = 2e3;
const WS_RECONNECT_MAX_DELAY = 6e4;

export { DAEMON_PING_URL as D, WS_RECONNECT_BASE_DELAY as W, DAEMON_WS_URL as a, WS_RECONNECT_MAX_DELAY as b };
227 changes: 157 additions & 70 deletions extension/dist/background.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
const DAEMON_PORT = 19825;
const DAEMON_HOST = "localhost";
const DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
const DAEMON_PING_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}/ping`;
const WS_RECONNECT_BASE_DELAY = 2e3;
const WS_RECONNECT_MAX_DELAY = 6e4;
import { D as DAEMON_PING_URL, a as DAEMON_WS_URL, W as WS_RECONNECT_BASE_DELAY, b as WS_RECONNECT_MAX_DELAY } from './assets/protocol-Z52ThYIj.js';

const attached = /* @__PURE__ */ new Set();
const BLANK_PAGE$1 = "data:text/html,<html></html>";
Expand Down Expand Up @@ -124,84 +119,159 @@ function registerListeners() {
});
}

let ws = null;
let reconnectTimer = null;
let reconnectAttempts = 0;
const _origLog = console.log.bind(console);
const _origWarn = console.warn.bind(console);
const _origError = console.error.bind(console);
function forwardLog(level, args) {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
const OFFSCREEN_URL = chrome.runtime.getURL("offscreen.html");
let forceLegacyTransport = false;
let legacyWs = null;
let legacyReconnectTimer = null;
let legacyReconnectAttempts = 0;
const MAX_EAGER_ATTEMPTS = 6;
function prefersOffscreenTransport() {
return !forceLegacyTransport && !!chrome.offscreen;
}
async function probeDaemon() {
try {
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
ws.send(JSON.stringify({ type: "log", level, msg, ts: Date.now() }));
const res = await fetch(DAEMON_PING_URL, { signal: AbortSignal.timeout(1e3) });
return res.ok;
} catch {
return false;
}
}
console.log = (...args) => {
_origLog(...args);
forwardLog("info", args);
};
console.warn = (...args) => {
_origWarn(...args);
forwardLog("warn", args);
};
console.error = (...args) => {
_origError(...args);
forwardLog("error", args);
};
async function connect() {
if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
async function ensureOffscreen() {
if (!chrome.offscreen) return false;
try {
const res = await fetch(DAEMON_PING_URL, { signal: AbortSignal.timeout(1e3) });
if (!res.ok) return;
} catch {
const existing = await chrome.offscreen.hasDocument();
if (!existing) {
await chrome.offscreen.createDocument({
url: OFFSCREEN_URL,
reasons: ["DOM_SCRAPING"],
justification: "Maintain persistent WebSocket connection to opencli daemon"
});
}
return true;
} catch (err) {
forceLegacyTransport = true;
console.warn("[opencli] Failed to initialize offscreen transport, falling back to Service Worker transport:", err);
return false;
}
}
async function offscreenConnect() {
const ready = await ensureOffscreen();
if (!ready) {
await legacyConnect();
return;
}
try {
ws = new WebSocket(DAEMON_WS_URL);
await chrome.runtime.sendMessage({ type: "ws-connect" });
} catch {
scheduleReconnect();
}
}
async function legacyConnect() {
if (legacyWs?.readyState === WebSocket.OPEN || legacyWs?.readyState === WebSocket.CONNECTING) return;
if (!await probeDaemon()) return;
try {
legacyWs = new WebSocket(DAEMON_WS_URL);
} catch {
scheduleLegacyReconnect();
return;
}
ws.onopen = () => {
legacyWs.onopen = () => {
console.log("[opencli] Connected to daemon");
reconnectAttempts = 0;
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
legacyReconnectAttempts = 0;
if (legacyReconnectTimer) {
clearTimeout(legacyReconnectTimer);
legacyReconnectTimer = null;
}
ws?.send(JSON.stringify({ type: "hello", version: chrome.runtime.getManifest().version }));
legacyWs?.send(JSON.stringify({ type: "hello", version: chrome.runtime.getManifest().version }));
};
ws.onmessage = async (event) => {
legacyWs.onmessage = async (event) => {
try {
const command = JSON.parse(event.data);
const result = await handleCommand(command);
ws?.send(JSON.stringify(result));
await wsSend(JSON.stringify(result));
} catch (err) {
console.error("[opencli] Message handling error:", err);
}
};
ws.onclose = () => {
legacyWs.onclose = () => {
console.log("[opencli] Disconnected from daemon");
ws = null;
scheduleReconnect();
legacyWs = null;
scheduleLegacyReconnect();
};
ws.onerror = () => {
ws?.close();
legacyWs.onerror = () => {
legacyWs?.close();
};
}
const MAX_EAGER_ATTEMPTS = 6;
function scheduleReconnect() {
if (reconnectTimer) return;
reconnectAttempts++;
if (reconnectAttempts > MAX_EAGER_ATTEMPTS) return;
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
void connect();
function scheduleLegacyReconnect() {
if (legacyReconnectTimer) return;
legacyReconnectAttempts++;
if (legacyReconnectAttempts > MAX_EAGER_ATTEMPTS) return;
const delay = Math.min(
WS_RECONNECT_BASE_DELAY * Math.pow(2, legacyReconnectAttempts - 1),
WS_RECONNECT_MAX_DELAY
);
legacyReconnectTimer = setTimeout(() => {
legacyReconnectTimer = null;
void legacyConnect();
}, delay);
}
async function connectTransport() {
if (prefersOffscreenTransport()) {
await offscreenConnect();
} else {
await legacyConnect();
}
}
async function wsSend(payload) {
if (!prefersOffscreenTransport()) {
if (legacyWs?.readyState === WebSocket.OPEN) {
legacyWs.send(payload);
} else {
void legacyConnect();
}
return;
}
try {
const resp = await chrome.runtime.sendMessage({ type: "ws-send", payload });
if (!resp?.ok) {
void offscreenConnect();
}
} catch {
void offscreenConnect();
}
}
async function wsStatus() {
if (!prefersOffscreenTransport()) {
return {
connected: legacyWs?.readyState === WebSocket.OPEN,
reconnecting: legacyReconnectTimer !== null
};
}
try {
const resp = await chrome.runtime.sendMessage({ type: "ws-status" });
return { connected: resp?.connected ?? false, reconnecting: resp?.reconnecting ?? false };
} catch {
return { connected: false, reconnecting: false };
}
}
const _origLog = console.log.bind(console);
const _origWarn = console.warn.bind(console);
const _origError = console.error.bind(console);
function forwardLog(level, args) {
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
void wsSend(JSON.stringify({ type: "log", level, msg, ts: Date.now() }));
}
console.log = (...args) => {
_origLog(...args);
forwardLog("info", args);
};
console.warn = (...args) => {
_origWarn(...args);
forwardLog("warn", args);
};
console.error = (...args) => {
_origError(...args);
forwardLog("error", args);
};
const automationSessions = /* @__PURE__ */ new Map();
const WINDOW_IDLE_TIMEOUT = 3e4;
function getWorkspaceKey(workspace) {
Expand Down Expand Up @@ -264,9 +334,9 @@ let initialized = false;
function initialize() {
if (initialized) return;
initialized = true;
chrome.alarms.create("keepalive", { periodInMinutes: 0.4 });
chrome.alarms.create("keepalive", { periodInMinutes: 0.25 });
registerListeners();
void connect();
void connectTransport();
console.log("[opencli] OpenCLI extension initialized");
}
chrome.runtime.onInstalled.addListener(() => {
Expand All @@ -276,14 +346,35 @@ chrome.runtime.onStartup.addListener(() => {
initialize();
});
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === "keepalive") void connect();
if (alarm.name === "keepalive") {
void connectTransport();
}
});
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
if (msg?.type === "getStatus") {
sendResponse({
connected: ws?.readyState === WebSocket.OPEN,
reconnecting: reconnectTimer !== null
});
wsStatus().then((s) => sendResponse(s));
return true;
}
if (msg?.type === "ws-probe") {
probeDaemon().then((ok) => sendResponse({ ok }));
return true;
}
if (msg?.type === "ws-message") {
sendResponse({ ok: true });
void (async () => {
try {
const command = JSON.parse(msg.data);
const result = await handleCommand(command);
await wsSend(JSON.stringify(result));
} catch (err) {
console.error("[opencli] Message handling error:", err);
}
})();
return false;
}
if (msg?.type === "log") {
void wsSend(JSON.stringify(msg));
return false;
}
return false;
});
Expand Down Expand Up @@ -435,17 +526,13 @@ async function handleNavigate(cmd, workspace) {
};
const listener = (id, info, tab2) => {
if (id !== tabId) return;
if (info.status === "complete" && isNavigationDone(tab2.url ?? info.url)) {
finish();
}
if (info.status === "complete" && isNavigationDone(tab2.url ?? info.url)) finish();
};
chrome.tabs.onUpdated.addListener(listener);
checkTimer = setTimeout(async () => {
try {
const currentTab = await chrome.tabs.get(tabId);
if (currentTab.status === "complete" && isNavigationDone(currentTab.url)) {
finish();
}
if (currentTab.status === "complete" && isNavigationDone(currentTab.url)) finish();
} catch {
}
}, 100);
Expand Down
Loading