refactor tab group sync to be reactive and handle edge cases

This commit is contained in:
Tommy D. Rossi
2025-12-19 19:22:36 +01:00
parent 5b474087eb
commit f97052f4f6
+73 -38
View File
@@ -9,6 +9,7 @@ let ws: WebSocket | null = null
let childSessions: Map<string, number> = new Map()
let nextSessionId = 1
let playwriterGroupId: number | null = null
let syncTabGroupQueue: Promise<void> = Promise.resolve()
const store = createStore<ExtensionState>(() => ({
tabs: new Map(),
@@ -95,43 +96,72 @@ function sendMessage(message: any): void {
}
}
async function addTabToGroup(tabId: number): Promise<void> {
async function syncTabGroup(): Promise<void> {
try {
if (playwriterGroupId !== null) {
const existingGroups = await chrome.tabGroups.query({ title: 'playwriter' })
if (!existingGroups.some((g) => g.id === playwriterGroupId)) {
playwriterGroupId = null
const connectedTabIds = Array.from(store.getState().tabs.entries())
.filter(([_, info]) => info.state === 'connected')
.map(([tabId]) => tabId)
const existingGroups = await chrome.tabGroups.query({ title: 'playwriter' })
if (existingGroups.length > 1) {
const [keep, ...duplicates] = existingGroups
for (const group of duplicates) {
const tabsInDupe = await chrome.tabs.query({ groupId: group.id })
for (const tab of tabsInDupe) {
if (tab.id) {
await chrome.tabs.ungroup(tab.id)
}
}
logger.debug('Removed duplicate playwriter group:', group.id)
}
playwriterGroupId = keep.id
} else if (existingGroups.length === 1) {
playwriterGroupId = existingGroups[0].id
}
if (connectedTabIds.length === 0) {
for (const group of existingGroups) {
const tabsInGroup = await chrome.tabs.query({ groupId: group.id })
for (const tab of tabsInGroup) {
if (tab.id) {
await chrome.tabs.ungroup(tab.id)
}
}
logger.debug('Cleared playwriter group:', group.id)
}
playwriterGroupId = null
return
}
const allTabs = await chrome.tabs.query({})
const tabsInGroup = allTabs.filter((t) => t.groupId === playwriterGroupId && t.id !== undefined)
const tabIdsInGroup = new Set(tabsInGroup.map((t) => t.id!))
const tabsToAdd = connectedTabIds.filter((id) => !tabIdsInGroup.has(id))
const tabsToRemove = Array.from(tabIdsInGroup).filter((id) => !connectedTabIds.includes(id))
for (const tabId of tabsToRemove) {
try {
await chrome.tabs.ungroup(tabId)
logger.debug('Removed tab from group:', tabId)
} catch (e: any) {
logger.debug('Failed to ungroup tab:', tabId, e.message)
}
}
if (playwriterGroupId === null) {
playwriterGroupId = await chrome.tabs.group({ tabIds: [tabId] })
await chrome.tabGroups.update(playwriterGroupId, { title: 'playwriter', color: 'green' })
logger.debug('Created tab group:', playwriterGroupId)
} else {
await chrome.tabs.group({ tabIds: [tabId], groupId: playwriterGroupId })
logger.debug('Added tab to existing group:', tabId)
}
} catch (error: any) {
logger.debug('Failed to add tab to group:', error.message)
}
}
async function removeTabFromGroup(tabId: number): Promise<void> {
try {
await chrome.tabs.ungroup(tabId)
logger.debug('Removed tab from group:', tabId)
if (playwriterGroupId !== null) {
const groups = await chrome.tabGroups.query({ title: 'playwriter' })
const group = groups.find((g) => g.id === playwriterGroupId)
if (!group) {
playwriterGroupId = null
logger.debug('Tab group was deleted')
if (tabsToAdd.length > 0) {
if (playwriterGroupId === null) {
playwriterGroupId = await chrome.tabs.group({ tabIds: tabsToAdd })
await chrome.tabGroups.update(playwriterGroupId, { title: 'playwriter', color: 'green' })
logger.debug('Created tab group:', playwriterGroupId, 'with tabs:', tabsToAdd)
} else {
await chrome.tabs.group({ tabIds: tabsToAdd, groupId: playwriterGroupId })
logger.debug('Added tabs to existing group:', tabsToAdd)
}
}
} catch (error: any) {
logger.debug('Failed to remove tab from group:', error.message)
logger.debug('Failed to sync tab group:', error.message)
}
}
@@ -340,7 +370,6 @@ async function attachTab(tabId: number): Promise<Protocol.Target.TargetInfo> {
})
logger.debug('Tab attached successfully:', tabId, 'sessionId:', sessionId, 'targetId:', targetInfo.targetId)
await addTabToGroup(tabId)
return targetInfo
}
@@ -353,8 +382,6 @@ function detachTab(tabId: number, shouldDetachDebugger: boolean): void {
logger.debug('Detaching tab:', tabId, 'sessionId:', tab.sessionId, 'shouldDetach:', shouldDetachDebugger)
void removeTabFromGroup(tabId)
sendMessage({
method: 'forwardCDPEvent',
params: {
@@ -397,7 +424,6 @@ function closeConnection(reason: string): void {
store.setState({ tabs: new Map(), connectionState: 'disconnected', errorText: undefined })
childSessions.clear()
playwriterGroupId = null
if (ws) {
ws.close(1000, reason)
@@ -420,7 +446,6 @@ function handleConnectionClose(reason: string, code: number): void {
}
childSessions.clear()
playwriterGroupId = null
ws = null
if (reason === 'Extension Replaced' || code === 4001) {
@@ -851,9 +876,19 @@ chrome.runtime.onInstalled.addListener((details) => {
}
})
store.subscribe(async () => {
logger.log(store.getState())
await updateIcons()
function serializeTabs(tabs: Map<number, TabInfo>): string {
return JSON.stringify(Array.from(tabs.entries()))
}
store.subscribe((state, prevState) => {
logger.log(state)
void updateIcons()
const tabsChanged = serializeTabs(state.tabs) !== serializeTabs(prevState.tabs)
if (tabsChanged) {
syncTabGroupQueue = syncTabGroupQueue.then(syncTabGroup).catch((e) => {
logger.debug('syncTabGroup error:', e)
})
}
})
logger.debug(`Using relay URL: ${RELAY_URL}`)