refactor tab group sync to be reactive and handle edge cases
This commit is contained in:
+73
-38
@@ -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}`)
|
||||
|
||||
Reference in New Issue
Block a user