@flrande/bak-extension 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.bak-e2e-build-stamp +1 -1
- package/dist/background.global.js +181 -29
- package/package.json +2 -2
- package/src/workspace.ts +200 -38
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-09T04:50:59.948Z
|
|
@@ -43,12 +43,7 @@
|
|
|
43
43
|
this.browser = browser;
|
|
44
44
|
}
|
|
45
45
|
async getWorkspaceInfo(workspaceId = DEFAULT_WORKSPACE_ID) {
|
|
46
|
-
|
|
47
|
-
if (!state) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
const repaired = await this.ensureWorkspace({ workspaceId, focus: false, initialUrl: DEFAULT_WORKSPACE_URL });
|
|
51
|
-
return repaired.workspace;
|
|
46
|
+
return this.inspectWorkspace(workspaceId);
|
|
52
47
|
}
|
|
53
48
|
async ensureWorkspace(options = {}) {
|
|
54
49
|
const workspaceId = this.normalizeWorkspaceId(options.workspaceId);
|
|
@@ -57,8 +52,19 @@
|
|
|
57
52
|
const persisted = await this.storage.load();
|
|
58
53
|
const created = !persisted;
|
|
59
54
|
let state = this.normalizeState(persisted, workspaceId);
|
|
60
|
-
|
|
55
|
+
const originalWindowId = state.windowId;
|
|
56
|
+
let window = state.windowId !== null ? await this.waitForWindow(state.windowId) : null;
|
|
61
57
|
let tabs = [];
|
|
58
|
+
if (!window) {
|
|
59
|
+
const rebound = await this.rebindWorkspaceWindow(state);
|
|
60
|
+
if (rebound) {
|
|
61
|
+
window = rebound.window;
|
|
62
|
+
tabs = rebound.tabs;
|
|
63
|
+
if (originalWindowId !== rebound.window.id) {
|
|
64
|
+
repairActions.push("rebound-window");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
62
68
|
if (!window) {
|
|
63
69
|
const createdWindow = await this.browser.createWindow({
|
|
64
70
|
url: initialUrl,
|
|
@@ -138,6 +144,12 @@
|
|
|
138
144
|
if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
|
|
139
145
|
tabs = [...tabs, activeTab];
|
|
140
146
|
}
|
|
147
|
+
if (tabs.length === 0 && state.primaryTabId !== null) {
|
|
148
|
+
const primaryTab = await this.waitForTrackedTab(state.primaryTabId, state.windowId);
|
|
149
|
+
if (primaryTab) {
|
|
150
|
+
tabs = [primaryTab];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
141
153
|
state.tabIds = [...new Set(tabs.map((tab) => tab.id))];
|
|
142
154
|
if (options.focus === true && state.activeTabId !== null) {
|
|
143
155
|
await this.browser.updateTab(state.activeTabId, { active: true });
|
|
@@ -162,18 +174,51 @@
|
|
|
162
174
|
focus: false,
|
|
163
175
|
initialUrl: options.url ?? DEFAULT_WORKSPACE_URL
|
|
164
176
|
});
|
|
165
|
-
|
|
177
|
+
let state = { ...ensured.workspace, tabIds: [...ensured.workspace.tabIds], tabs: [...ensured.workspace.tabs] };
|
|
178
|
+
if (state.windowId !== null && state.tabs.length === 0) {
|
|
179
|
+
const rebound = await this.rebindWorkspaceWindow(state);
|
|
180
|
+
if (rebound) {
|
|
181
|
+
state.windowId = rebound.window.id;
|
|
182
|
+
state.tabs = rebound.tabs;
|
|
183
|
+
state.tabIds = [...new Set(rebound.tabs.map((tab2) => tab2.id))];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
166
186
|
const active = options.active === true;
|
|
167
187
|
const desiredUrl = options.url ?? DEFAULT_WORKSPACE_URL;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
188
|
+
let reusablePrimaryTab = await this.resolveReusablePrimaryTab(
|
|
189
|
+
state,
|
|
190
|
+
ensured.created || ensured.repairActions.includes("recreated-window") || ensured.repairActions.includes("created-primary-tab")
|
|
191
|
+
);
|
|
192
|
+
let createdTab;
|
|
193
|
+
try {
|
|
194
|
+
createdTab = reusablePrimaryTab ? await this.browser.updateTab(reusablePrimaryTab.id, {
|
|
195
|
+
url: desiredUrl,
|
|
196
|
+
active
|
|
197
|
+
}) : await this.createWorkspaceTab({
|
|
198
|
+
windowId: state.windowId,
|
|
199
|
+
url: desiredUrl,
|
|
200
|
+
active
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (!this.isMissingWindowError(error)) {
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
const repaired = await this.ensureWorkspace({
|
|
207
|
+
workspaceId: options.workspaceId,
|
|
208
|
+
focus: false,
|
|
209
|
+
initialUrl: desiredUrl
|
|
210
|
+
});
|
|
211
|
+
state = { ...repaired.workspace };
|
|
212
|
+
reusablePrimaryTab = await this.resolveReusablePrimaryTab(state, true);
|
|
213
|
+
createdTab = reusablePrimaryTab ? await this.browser.updateTab(reusablePrimaryTab.id, {
|
|
214
|
+
url: desiredUrl,
|
|
215
|
+
active
|
|
216
|
+
}) : await this.createWorkspaceTab({
|
|
217
|
+
windowId: state.windowId,
|
|
218
|
+
url: desiredUrl,
|
|
219
|
+
active
|
|
220
|
+
});
|
|
221
|
+
}
|
|
177
222
|
const nextTabIds = [.../* @__PURE__ */ new Set([...state.tabIds, createdTab.id])];
|
|
178
223
|
const groupId = await this.browser.groupTabs([createdTab.id], state.groupId ?? void 0);
|
|
179
224
|
await this.browser.updateGroup(groupId, {
|
|
@@ -207,17 +252,30 @@
|
|
|
207
252
|
};
|
|
208
253
|
}
|
|
209
254
|
async listTabs(workspaceId = DEFAULT_WORKSPACE_ID) {
|
|
210
|
-
const ensured = await this.
|
|
255
|
+
const ensured = await this.inspectWorkspace(workspaceId);
|
|
256
|
+
if (!ensured) {
|
|
257
|
+
throw new Error(`Workspace ${workspaceId} does not exist`);
|
|
258
|
+
}
|
|
211
259
|
return {
|
|
212
|
-
workspace: ensured
|
|
213
|
-
tabs: ensured.
|
|
260
|
+
workspace: ensured,
|
|
261
|
+
tabs: ensured.tabs
|
|
214
262
|
};
|
|
215
263
|
}
|
|
216
264
|
async getActiveTab(workspaceId = DEFAULT_WORKSPACE_ID) {
|
|
217
|
-
const ensured = await this.
|
|
265
|
+
const ensured = await this.inspectWorkspace(workspaceId);
|
|
266
|
+
if (!ensured) {
|
|
267
|
+
const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
|
|
268
|
+
return {
|
|
269
|
+
workspace: {
|
|
270
|
+
...this.normalizeState(null, normalizedWorkspaceId),
|
|
271
|
+
tabs: []
|
|
272
|
+
},
|
|
273
|
+
tab: null
|
|
274
|
+
};
|
|
275
|
+
}
|
|
218
276
|
return {
|
|
219
|
-
workspace: ensured
|
|
220
|
-
tab: ensured.
|
|
277
|
+
workspace: ensured,
|
|
278
|
+
tab: ensured.tabs.find((tab) => tab.id === ensured.activeTabId) ?? null
|
|
221
279
|
};
|
|
222
280
|
}
|
|
223
281
|
async setActiveTab(tabId, workspaceId = DEFAULT_WORKSPACE_ID) {
|
|
@@ -413,6 +471,60 @@
|
|
|
413
471
|
)).filter((tab) => tab !== null);
|
|
414
472
|
return tabs;
|
|
415
473
|
}
|
|
474
|
+
async readLooseTrackedTabs(tabIds) {
|
|
475
|
+
const tabs = (await Promise.all(
|
|
476
|
+
tabIds.map(async (tabId) => {
|
|
477
|
+
return await this.browser.getTab(tabId);
|
|
478
|
+
})
|
|
479
|
+
)).filter((tab) => tab !== null);
|
|
480
|
+
return tabs;
|
|
481
|
+
}
|
|
482
|
+
collectCandidateTabIds(state) {
|
|
483
|
+
return [...new Set(state.tabIds.concat([state.activeTabId, state.primaryTabId].filter((value) => typeof value === "number")))];
|
|
484
|
+
}
|
|
485
|
+
async rebindWorkspaceWindow(state) {
|
|
486
|
+
const candidateWindowIds = [];
|
|
487
|
+
const pushWindowId = (windowId) => {
|
|
488
|
+
if (typeof windowId !== "number") {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (!candidateWindowIds.includes(windowId)) {
|
|
492
|
+
candidateWindowIds.push(windowId);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
const group = state.groupId !== null ? await this.browser.getGroup(state.groupId) : null;
|
|
496
|
+
pushWindowId(group?.windowId);
|
|
497
|
+
const trackedTabs = await this.readLooseTrackedTabs(this.collectCandidateTabIds(state));
|
|
498
|
+
for (const tab of trackedTabs) {
|
|
499
|
+
pushWindowId(tab.windowId);
|
|
500
|
+
}
|
|
501
|
+
for (const candidateWindowId of candidateWindowIds) {
|
|
502
|
+
const window = await this.waitForWindow(candidateWindowId);
|
|
503
|
+
if (!window) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
let tabs = await this.readTrackedTabs(this.collectCandidateTabIds(state), candidateWindowId);
|
|
507
|
+
if (tabs.length === 0 && group?.id !== null && group?.windowId === candidateWindowId) {
|
|
508
|
+
const windowTabs = await this.waitForWindowTabs(candidateWindowId, 750);
|
|
509
|
+
tabs = windowTabs.filter((tab) => tab.groupId === group.id);
|
|
510
|
+
}
|
|
511
|
+
if (tabs.length === 0) {
|
|
512
|
+
tabs = trackedTabs.filter((tab) => tab.windowId === candidateWindowId);
|
|
513
|
+
}
|
|
514
|
+
state.windowId = candidateWindowId;
|
|
515
|
+
if (tabs.length > 0) {
|
|
516
|
+
state.tabIds = [...new Set(tabs.map((tab) => tab.id))];
|
|
517
|
+
if (state.primaryTabId === null || !state.tabIds.includes(state.primaryTabId)) {
|
|
518
|
+
state.primaryTabId = tabs[0]?.id ?? null;
|
|
519
|
+
}
|
|
520
|
+
if (state.activeTabId === null || !state.tabIds.includes(state.activeTabId)) {
|
|
521
|
+
state.activeTabId = tabs.find((tab) => tab.active)?.id ?? state.primaryTabId;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return { window, tabs };
|
|
525
|
+
}
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
416
528
|
async recoverWorkspaceTabs(state, existingTabs) {
|
|
417
529
|
if (state.windowId === null) {
|
|
418
530
|
return existingTabs;
|
|
@@ -446,12 +558,6 @@
|
|
|
446
558
|
const deadline = Date.now() + 1500;
|
|
447
559
|
let lastError2 = null;
|
|
448
560
|
while (Date.now() < deadline) {
|
|
449
|
-
const window = await this.browser.getWindow(options.windowId);
|
|
450
|
-
if (!window) {
|
|
451
|
-
lastError2 = new Error(`No window with id: ${options.windowId}.`);
|
|
452
|
-
await this.delay(50);
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
561
|
try {
|
|
456
562
|
return await this.browser.createTab({
|
|
457
563
|
windowId: options.windowId,
|
|
@@ -468,6 +574,52 @@
|
|
|
468
574
|
}
|
|
469
575
|
throw lastError2 ?? new Error(`No window with id: ${options.windowId}.`);
|
|
470
576
|
}
|
|
577
|
+
async inspectWorkspace(workspaceId = DEFAULT_WORKSPACE_ID) {
|
|
578
|
+
const state = await this.loadWorkspaceRecord(workspaceId);
|
|
579
|
+
if (!state) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
let tabs = await this.readTrackedTabs(state.tabIds, state.windowId);
|
|
583
|
+
const activeTab = state.activeTabId !== null ? await this.waitForTrackedTab(state.activeTabId, state.windowId, 300) : null;
|
|
584
|
+
if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
|
|
585
|
+
tabs = [...tabs, activeTab];
|
|
586
|
+
}
|
|
587
|
+
if (tabs.length === 0 && state.primaryTabId !== null) {
|
|
588
|
+
const primaryTab = await this.waitForTrackedTab(state.primaryTabId, state.windowId, 300);
|
|
589
|
+
if (primaryTab) {
|
|
590
|
+
tabs = [primaryTab];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
...state,
|
|
595
|
+
tabIds: [...new Set(state.tabIds.concat(tabs.map((tab) => tab.id)))],
|
|
596
|
+
tabs
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async resolveReusablePrimaryTab(workspace, allowReuse) {
|
|
600
|
+
if (!allowReuse || workspace.windowId === null) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
if (workspace.primaryTabId !== null) {
|
|
604
|
+
const trackedPrimary = workspace.tabs.find((tab) => tab.id === workspace.primaryTabId) ?? await this.waitForTrackedTab(workspace.primaryTabId, workspace.windowId);
|
|
605
|
+
if (trackedPrimary) {
|
|
606
|
+
return trackedPrimary;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const windowTabs = await this.waitForWindowTabs(workspace.windowId, 750);
|
|
610
|
+
return windowTabs.length === 1 ? windowTabs[0] : null;
|
|
611
|
+
}
|
|
612
|
+
async waitForWindow(windowId, timeoutMs = 750) {
|
|
613
|
+
const deadline = Date.now() + timeoutMs;
|
|
614
|
+
while (Date.now() < deadline) {
|
|
615
|
+
const window = await this.browser.getWindow(windowId);
|
|
616
|
+
if (window) {
|
|
617
|
+
return window;
|
|
618
|
+
}
|
|
619
|
+
await this.delay(50);
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
471
623
|
async waitForTrackedTab(tabId, windowId, timeoutMs = 1e3) {
|
|
472
624
|
const deadline = Date.now() + timeoutMs;
|
|
473
625
|
while (Date.now() < deadline) {
|
package/package.json
CHANGED
package/src/workspace.ts
CHANGED
|
@@ -108,12 +108,7 @@ export class WorkspaceManager {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
async getWorkspaceInfo(workspaceId = DEFAULT_WORKSPACE_ID): Promise<WorkspaceInfo | null> {
|
|
111
|
-
|
|
112
|
-
if (!state) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
const repaired = await this.ensureWorkspace({ workspaceId, focus: false, initialUrl: DEFAULT_WORKSPACE_URL });
|
|
116
|
-
return repaired.workspace;
|
|
111
|
+
return this.inspectWorkspace(workspaceId);
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
async ensureWorkspace(options: WorkspaceEnsureOptions = {}): Promise<WorkspaceEnsureResult> {
|
|
@@ -124,8 +119,19 @@ export class WorkspaceManager {
|
|
|
124
119
|
const created = !persisted;
|
|
125
120
|
let state = this.normalizeState(persisted, workspaceId);
|
|
126
121
|
|
|
127
|
-
|
|
122
|
+
const originalWindowId = state.windowId;
|
|
123
|
+
let window = state.windowId !== null ? await this.waitForWindow(state.windowId) : null;
|
|
128
124
|
let tabs: WorkspaceTab[] = [];
|
|
125
|
+
if (!window) {
|
|
126
|
+
const rebound = await this.rebindWorkspaceWindow(state);
|
|
127
|
+
if (rebound) {
|
|
128
|
+
window = rebound.window;
|
|
129
|
+
tabs = rebound.tabs;
|
|
130
|
+
if (originalWindowId !== rebound.window.id) {
|
|
131
|
+
repairActions.push('rebound-window');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
129
135
|
if (!window) {
|
|
130
136
|
const createdWindow = await this.browser.createWindow({
|
|
131
137
|
url: initialUrl,
|
|
@@ -212,6 +218,12 @@ export class WorkspaceManager {
|
|
|
212
218
|
if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
|
|
213
219
|
tabs = [...tabs, activeTab];
|
|
214
220
|
}
|
|
221
|
+
if (tabs.length === 0 && state.primaryTabId !== null) {
|
|
222
|
+
const primaryTab = await this.waitForTrackedTab(state.primaryTabId, state.windowId);
|
|
223
|
+
if (primaryTab) {
|
|
224
|
+
tabs = [primaryTab];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
215
227
|
state.tabIds = [...new Set(tabs.map((tab) => tab.id))];
|
|
216
228
|
|
|
217
229
|
if (options.focus === true && state.activeTabId !== null) {
|
|
@@ -240,26 +252,56 @@ export class WorkspaceManager {
|
|
|
240
252
|
focus: false,
|
|
241
253
|
initialUrl: options.url ?? DEFAULT_WORKSPACE_URL
|
|
242
254
|
});
|
|
243
|
-
|
|
255
|
+
let state = { ...ensured.workspace, tabIds: [...ensured.workspace.tabIds], tabs: [...ensured.workspace.tabs] };
|
|
256
|
+
if (state.windowId !== null && state.tabs.length === 0) {
|
|
257
|
+
const rebound = await this.rebindWorkspaceWindow(state);
|
|
258
|
+
if (rebound) {
|
|
259
|
+
state.windowId = rebound.window.id;
|
|
260
|
+
state.tabs = rebound.tabs;
|
|
261
|
+
state.tabIds = [...new Set(rebound.tabs.map((tab) => tab.id))];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
244
264
|
const active = options.active === true;
|
|
245
265
|
const desiredUrl = options.url ?? DEFAULT_WORKSPACE_URL;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
266
|
+
let reusablePrimaryTab = await this.resolveReusablePrimaryTab(
|
|
267
|
+
state,
|
|
268
|
+
ensured.created || ensured.repairActions.includes('recreated-window') || ensured.repairActions.includes('created-primary-tab')
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
let createdTab: WorkspaceTab;
|
|
272
|
+
try {
|
|
273
|
+
createdTab = reusablePrimaryTab
|
|
274
|
+
? await this.browser.updateTab(reusablePrimaryTab.id, {
|
|
275
|
+
url: desiredUrl,
|
|
276
|
+
active
|
|
277
|
+
})
|
|
278
|
+
: await this.createWorkspaceTab({
|
|
279
|
+
windowId: state.windowId,
|
|
280
|
+
url: desiredUrl,
|
|
281
|
+
active
|
|
282
|
+
});
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (!this.isMissingWindowError(error)) {
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
const repaired = await this.ensureWorkspace({
|
|
288
|
+
workspaceId: options.workspaceId,
|
|
289
|
+
focus: false,
|
|
290
|
+
initialUrl: desiredUrl
|
|
291
|
+
});
|
|
292
|
+
state = { ...repaired.workspace };
|
|
293
|
+
reusablePrimaryTab = await this.resolveReusablePrimaryTab(state, true);
|
|
294
|
+
createdTab = reusablePrimaryTab
|
|
295
|
+
? await this.browser.updateTab(reusablePrimaryTab.id, {
|
|
296
|
+
url: desiredUrl,
|
|
297
|
+
active
|
|
298
|
+
})
|
|
299
|
+
: await this.createWorkspaceTab({
|
|
300
|
+
windowId: state.windowId,
|
|
301
|
+
url: desiredUrl,
|
|
302
|
+
active
|
|
303
|
+
});
|
|
304
|
+
}
|
|
263
305
|
const nextTabIds = [...new Set([...state.tabIds, createdTab.id])];
|
|
264
306
|
const groupId = await this.browser.groupTabs([createdTab.id], state.groupId ?? undefined);
|
|
265
307
|
await this.browser.updateGroup(groupId, {
|
|
@@ -296,18 +338,31 @@ export class WorkspaceManager {
|
|
|
296
338
|
}
|
|
297
339
|
|
|
298
340
|
async listTabs(workspaceId = DEFAULT_WORKSPACE_ID): Promise<{ workspace: WorkspaceInfo; tabs: WorkspaceTab[] }> {
|
|
299
|
-
const ensured = await this.
|
|
341
|
+
const ensured = await this.inspectWorkspace(workspaceId);
|
|
342
|
+
if (!ensured) {
|
|
343
|
+
throw new Error(`Workspace ${workspaceId} does not exist`);
|
|
344
|
+
}
|
|
300
345
|
return {
|
|
301
|
-
workspace: ensured
|
|
302
|
-
tabs: ensured.
|
|
346
|
+
workspace: ensured,
|
|
347
|
+
tabs: ensured.tabs
|
|
303
348
|
};
|
|
304
349
|
}
|
|
305
350
|
|
|
306
351
|
async getActiveTab(workspaceId = DEFAULT_WORKSPACE_ID): Promise<{ workspace: WorkspaceInfo; tab: WorkspaceTab | null }> {
|
|
307
|
-
const ensured = await this.
|
|
352
|
+
const ensured = await this.inspectWorkspace(workspaceId);
|
|
353
|
+
if (!ensured) {
|
|
354
|
+
const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
|
|
355
|
+
return {
|
|
356
|
+
workspace: {
|
|
357
|
+
...this.normalizeState(null, normalizedWorkspaceId),
|
|
358
|
+
tabs: []
|
|
359
|
+
},
|
|
360
|
+
tab: null
|
|
361
|
+
};
|
|
362
|
+
}
|
|
308
363
|
return {
|
|
309
|
-
workspace: ensured
|
|
310
|
-
tab: ensured.
|
|
364
|
+
workspace: ensured,
|
|
365
|
+
tab: ensured.tabs.find((tab) => tab.id === ensured.activeTabId) ?? null
|
|
311
366
|
};
|
|
312
367
|
}
|
|
313
368
|
|
|
@@ -525,6 +580,69 @@ export class WorkspaceManager {
|
|
|
525
580
|
return tabs;
|
|
526
581
|
}
|
|
527
582
|
|
|
583
|
+
private async readLooseTrackedTabs(tabIds: number[]): Promise<WorkspaceTab[]> {
|
|
584
|
+
const tabs = (
|
|
585
|
+
await Promise.all(
|
|
586
|
+
tabIds.map(async (tabId) => {
|
|
587
|
+
return await this.browser.getTab(tabId);
|
|
588
|
+
})
|
|
589
|
+
)
|
|
590
|
+
).filter((tab): tab is WorkspaceTab => tab !== null);
|
|
591
|
+
return tabs;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private collectCandidateTabIds(state: WorkspaceRecord): number[] {
|
|
595
|
+
return [...new Set(state.tabIds.concat([state.activeTabId, state.primaryTabId].filter((value): value is number => typeof value === 'number')))];
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
private async rebindWorkspaceWindow(state: WorkspaceRecord): Promise<{ window: WorkspaceWindow; tabs: WorkspaceTab[] } | null> {
|
|
599
|
+
const candidateWindowIds: number[] = [];
|
|
600
|
+
const pushWindowId = (windowId: number | null | undefined): void => {
|
|
601
|
+
if (typeof windowId !== 'number') {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (!candidateWindowIds.includes(windowId)) {
|
|
605
|
+
candidateWindowIds.push(windowId);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const group = state.groupId !== null ? await this.browser.getGroup(state.groupId) : null;
|
|
610
|
+
pushWindowId(group?.windowId);
|
|
611
|
+
|
|
612
|
+
const trackedTabs = await this.readLooseTrackedTabs(this.collectCandidateTabIds(state));
|
|
613
|
+
for (const tab of trackedTabs) {
|
|
614
|
+
pushWindowId(tab.windowId);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
for (const candidateWindowId of candidateWindowIds) {
|
|
618
|
+
const window = await this.waitForWindow(candidateWindowId);
|
|
619
|
+
if (!window) {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
let tabs = await this.readTrackedTabs(this.collectCandidateTabIds(state), candidateWindowId);
|
|
623
|
+
if (tabs.length === 0 && group?.id !== null && group?.windowId === candidateWindowId) {
|
|
624
|
+
const windowTabs = await this.waitForWindowTabs(candidateWindowId, 750);
|
|
625
|
+
tabs = windowTabs.filter((tab) => tab.groupId === group.id);
|
|
626
|
+
}
|
|
627
|
+
if (tabs.length === 0) {
|
|
628
|
+
tabs = trackedTabs.filter((tab) => tab.windowId === candidateWindowId);
|
|
629
|
+
}
|
|
630
|
+
state.windowId = candidateWindowId;
|
|
631
|
+
if (tabs.length > 0) {
|
|
632
|
+
state.tabIds = [...new Set(tabs.map((tab) => tab.id))];
|
|
633
|
+
if (state.primaryTabId === null || !state.tabIds.includes(state.primaryTabId)) {
|
|
634
|
+
state.primaryTabId = tabs[0]?.id ?? null;
|
|
635
|
+
}
|
|
636
|
+
if (state.activeTabId === null || !state.tabIds.includes(state.activeTabId)) {
|
|
637
|
+
state.activeTabId = tabs.find((tab) => tab.active)?.id ?? state.primaryTabId;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return { window, tabs };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
|
|
528
646
|
private async recoverWorkspaceTabs(state: WorkspaceRecord, existingTabs: WorkspaceTab[]): Promise<WorkspaceTab[]> {
|
|
529
647
|
if (state.windowId === null) {
|
|
530
648
|
return existingTabs;
|
|
@@ -566,13 +684,6 @@ export class WorkspaceManager {
|
|
|
566
684
|
let lastError: Error | null = null;
|
|
567
685
|
|
|
568
686
|
while (Date.now() < deadline) {
|
|
569
|
-
const window = await this.browser.getWindow(options.windowId);
|
|
570
|
-
if (!window) {
|
|
571
|
-
lastError = new Error(`No window with id: ${options.windowId}.`);
|
|
572
|
-
await this.delay(50);
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
687
|
try {
|
|
577
688
|
return await this.browser.createTab({
|
|
578
689
|
windowId: options.windowId,
|
|
@@ -591,6 +702,57 @@ export class WorkspaceManager {
|
|
|
591
702
|
throw lastError ?? new Error(`No window with id: ${options.windowId}.`);
|
|
592
703
|
}
|
|
593
704
|
|
|
705
|
+
private async inspectWorkspace(workspaceId = DEFAULT_WORKSPACE_ID): Promise<WorkspaceInfo | null> {
|
|
706
|
+
const state = await this.loadWorkspaceRecord(workspaceId);
|
|
707
|
+
if (!state) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let tabs = await this.readTrackedTabs(state.tabIds, state.windowId);
|
|
712
|
+
const activeTab = state.activeTabId !== null ? await this.waitForTrackedTab(state.activeTabId, state.windowId, 300) : null;
|
|
713
|
+
if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
|
|
714
|
+
tabs = [...tabs, activeTab];
|
|
715
|
+
}
|
|
716
|
+
if (tabs.length === 0 && state.primaryTabId !== null) {
|
|
717
|
+
const primaryTab = await this.waitForTrackedTab(state.primaryTabId, state.windowId, 300);
|
|
718
|
+
if (primaryTab) {
|
|
719
|
+
tabs = [primaryTab];
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return {
|
|
724
|
+
...state,
|
|
725
|
+
tabIds: [...new Set(state.tabIds.concat(tabs.map((tab) => tab.id)))],
|
|
726
|
+
tabs
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
private async resolveReusablePrimaryTab(workspace: WorkspaceInfo, allowReuse: boolean): Promise<WorkspaceTab | null> {
|
|
731
|
+
if (!allowReuse || workspace.windowId === null) {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
if (workspace.primaryTabId !== null) {
|
|
735
|
+
const trackedPrimary = workspace.tabs.find((tab) => tab.id === workspace.primaryTabId) ?? (await this.waitForTrackedTab(workspace.primaryTabId, workspace.windowId));
|
|
736
|
+
if (trackedPrimary) {
|
|
737
|
+
return trackedPrimary;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const windowTabs = await this.waitForWindowTabs(workspace.windowId, 750);
|
|
741
|
+
return windowTabs.length === 1 ? windowTabs[0]! : null;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private async waitForWindow(windowId: number, timeoutMs = 750): Promise<WorkspaceWindow | null> {
|
|
745
|
+
const deadline = Date.now() + timeoutMs;
|
|
746
|
+
while (Date.now() < deadline) {
|
|
747
|
+
const window = await this.browser.getWindow(windowId);
|
|
748
|
+
if (window) {
|
|
749
|
+
return window;
|
|
750
|
+
}
|
|
751
|
+
await this.delay(50);
|
|
752
|
+
}
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
|
|
594
756
|
private async waitForTrackedTab(tabId: number, windowId: number | null, timeoutMs = 1_000): Promise<WorkspaceTab | null> {
|
|
595
757
|
const deadline = Date.now() + timeoutMs;
|
|
596
758
|
while (Date.now() < deadline) {
|