feat: add CDP tab-group plugin handshake with silent fallback

This commit is contained in:
leeguooooo
2026-03-03 12:17:14 +09:00
parent d04cf59238
commit 2a766cfe48
19 changed files with 1079 additions and 249 deletions
@@ -0,0 +1,74 @@
(() => {
const REQUEST_TYPE = 'AB_TAB_GROUP_REQUEST';
const RESPONSE_TYPE = 'AB_TAB_GROUP_RESPONSE';
window.addEventListener('message', (event) => {
if (event.source !== window) {
return;
}
const data = event.data;
if (!data || data.type !== REQUEST_TYPE) {
return;
}
let request;
try {
request = {
type: REQUEST_TYPE,
nonce: data.nonce,
session: data.session,
groupTitle: data.groupTitle,
pluginId: data.pluginId,
};
} catch {
return;
}
try {
chrome.runtime.sendMessage(request, (response) => {
const lastError = chrome.runtime.lastError;
if (lastError) {
window.postMessage(
{
type: RESPONSE_TYPE,
nonce: request.nonce,
ok: false,
error: lastError.message,
},
'*'
);
return;
}
const payload = response && typeof response === 'object' ? response : { ok: false };
window.postMessage(
{
type: RESPONSE_TYPE,
nonce: request.nonce,
ok: payload.ok === true,
extensionId:
typeof payload.extensionId === 'string' && payload.extensionId.length > 0
? payload.extensionId
: chrome.runtime.id,
groupId: typeof payload.groupId === 'number' ? payload.groupId : undefined,
error: typeof payload.error === 'string' ? payload.error : undefined,
},
'*'
);
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
window.postMessage(
{
type: RESPONSE_TYPE,
nonce: request.nonce,
ok: false,
error: errorMessage,
},
'*'
);
}
});
})();
+19
View File
@@ -0,0 +1,19 @@
{
"manifest_version": 3,
"name": "Agent Browser CDP Tab Grouper",
"version": "0.1.0",
"description": "Groups tabs by Agent Browser session when requested from CDP-driven pages.",
"permissions": ["tabs", "tabGroups"],
"host_permissions": ["<all_urls>"],
"background": {
"service_worker": "service-worker.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-script.js"],
"run_at": "document_start",
"match_about_blank": true
}
]
}
+134
View File
@@ -0,0 +1,134 @@
const REQUEST_TYPE = 'AB_TAB_GROUP_REQUEST';
const DEFAULT_GROUP_TITLE = 'Agent Browser Stealth';
const sessionGroupCache = new Map();
function normalizeSession(session) {
if (typeof session !== 'string') return 'default';
const trimmed = session.trim();
return trimmed.length > 0 ? trimmed.slice(0, 64) : 'default';
}
function normalizeGroupTitle(title) {
if (typeof title !== 'string') return DEFAULT_GROUP_TITLE;
const trimmed = title.trim();
return trimmed.length > 0 ? trimmed.slice(0, 80) : DEFAULT_GROUP_TITLE;
}
function cacheKey(windowId, session) {
return `${windowId}:${session}`;
}
async function findExistingGroup(windowId, groupTitle) {
const tabs = await chrome.tabs.query({ windowId });
const checked = new Set();
for (const tab of tabs) {
if (typeof tab.groupId !== 'number' || tab.groupId < 0 || checked.has(tab.groupId)) {
continue;
}
checked.add(tab.groupId);
try {
const group = await chrome.tabGroups.get(tab.groupId);
if (group.title === groupTitle) {
return tab.groupId;
}
} catch {
// Ignore stale group references and continue.
}
}
return null;
}
async function ensureSessionGroup(tabId, windowId, session, groupTitle) {
const key = cacheKey(windowId, session);
let groupId = sessionGroupCache.get(key);
if (typeof groupId === 'number') {
try {
await chrome.tabGroups.get(groupId);
} catch {
groupId = undefined;
}
}
if (typeof groupId !== 'number') {
const existing = await findExistingGroup(windowId, groupTitle);
if (typeof existing === 'number') {
groupId = existing;
}
}
if (typeof groupId === 'number') {
await chrome.tabs.group({ groupId, tabIds: [tabId] });
} else {
groupId = await chrome.tabs.group({
tabIds: [tabId],
createProperties: { windowId },
});
}
await chrome.tabGroups.update(groupId, {
title: groupTitle,
color: 'blue',
collapsed: false,
});
sessionGroupCache.set(key, groupId);
return groupId;
}
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (!message || message.type !== REQUEST_TYPE) {
return;
}
const tabId = sender.tab?.id;
const windowId = sender.tab?.windowId;
const nonce = typeof message.nonce === 'string' ? message.nonce : undefined;
if (typeof tabId !== 'number' || typeof windowId !== 'number') {
sendResponse({
ok: false,
error: 'missing-tab-context',
extensionId: chrome.runtime.id,
nonce,
});
return;
}
if (typeof message.pluginId === 'string' && message.pluginId !== chrome.runtime.id) {
sendResponse({
ok: false,
error: 'plugin-id-mismatch',
extensionId: chrome.runtime.id,
nonce,
});
return;
}
const session = normalizeSession(message.session);
const groupTitle = normalizeGroupTitle(message.groupTitle);
ensureSessionGroup(tabId, windowId, session, groupTitle)
.then((groupId) => {
sendResponse({
ok: true,
groupId,
extensionId: chrome.runtime.id,
nonce,
});
})
.catch((error) => {
const errorMessage = error instanceof Error ? error.message : String(error);
sendResponse({
ok: false,
error: errorMessage,
extensionId: chrome.runtime.id,
nonce,
});
});
return true;
});