@flrande/bak-extension 0.6.0 → 0.6.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 +692 -266
- package/dist/content.global.js +83 -22
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/public/manifest.json +1 -1
- package/src/background.ts +728 -202
- package/src/content.ts +93 -22
- package/src/network-debugger.ts +1 -1
- package/src/session-binding-storage.ts +50 -68
- package/src/{workspace.ts → session-binding.ts} +228 -213
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
export const
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
1
|
+
export const DEFAULT_SESSION_BINDING_LABEL = 'bak agent';
|
|
2
|
+
export const DEFAULT_SESSION_BINDING_COLOR = 'blue';
|
|
3
|
+
export const DEFAULT_SESSION_BINDING_URL = 'about:blank';
|
|
4
|
+
const WINDOW_LOOKUP_TIMEOUT_MS = 1_500;
|
|
5
|
+
const GROUP_LOOKUP_TIMEOUT_MS = 1_000;
|
|
6
|
+
const WINDOW_TABS_LOOKUP_TIMEOUT_MS = 1_500;
|
|
4
7
|
|
|
5
|
-
export type
|
|
8
|
+
export type SessionBindingColor = 'grey' | 'blue' | 'red' | 'yellow' | 'green' | 'pink' | 'purple' | 'cyan' | 'orange';
|
|
6
9
|
|
|
7
|
-
export interface
|
|
10
|
+
export interface SessionBindingTab {
|
|
8
11
|
id: number;
|
|
9
12
|
title: string;
|
|
10
13
|
url: string;
|
|
@@ -13,23 +16,23 @@ export interface WorkspaceTab {
|
|
|
13
16
|
groupId: number | null;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
export interface
|
|
19
|
+
export interface SessionBindingWindow {
|
|
17
20
|
id: number;
|
|
18
21
|
focused: boolean;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
export interface
|
|
24
|
+
export interface SessionBindingGroup {
|
|
22
25
|
id: number;
|
|
23
26
|
windowId: number;
|
|
24
27
|
title: string;
|
|
25
|
-
color:
|
|
28
|
+
color: SessionBindingColor;
|
|
26
29
|
collapsed: boolean;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
export interface
|
|
32
|
+
export interface SessionBindingRecord {
|
|
30
33
|
id: string;
|
|
31
34
|
label: string;
|
|
32
|
-
color:
|
|
35
|
+
color: SessionBindingColor;
|
|
33
36
|
windowId: number | null;
|
|
34
37
|
groupId: number | null;
|
|
35
38
|
tabIds: number[];
|
|
@@ -37,99 +40,99 @@ export interface WorkspaceRecord {
|
|
|
37
40
|
primaryTabId: number | null;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
export interface
|
|
41
|
-
tabs:
|
|
43
|
+
export interface SessionBindingInfo extends SessionBindingRecord {
|
|
44
|
+
tabs: SessionBindingTab[];
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
export interface
|
|
45
|
-
|
|
47
|
+
export interface SessionBindingEnsureResult {
|
|
48
|
+
binding: SessionBindingInfo;
|
|
46
49
|
created: boolean;
|
|
47
50
|
repaired: boolean;
|
|
48
51
|
repairActions: string[];
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
export interface
|
|
52
|
-
tab:
|
|
53
|
-
|
|
54
|
-
resolution: 'explicit-tab' | 'explicit-
|
|
55
|
-
|
|
54
|
+
export interface SessionBindingTargetResolution {
|
|
55
|
+
tab: SessionBindingTab;
|
|
56
|
+
binding: SessionBindingInfo | null;
|
|
57
|
+
resolution: 'explicit-tab' | 'explicit-binding' | 'default-binding' | 'browser-active';
|
|
58
|
+
createdBinding: boolean;
|
|
56
59
|
repaired: boolean;
|
|
57
60
|
repairActions: string[];
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
export interface
|
|
61
|
-
load(
|
|
62
|
-
save(state:
|
|
63
|
-
delete(
|
|
64
|
-
list(): Promise<
|
|
63
|
+
export interface SessionBindingStorage {
|
|
64
|
+
load(bindingId: string): Promise<SessionBindingRecord | null>;
|
|
65
|
+
save(state: SessionBindingRecord): Promise<void>;
|
|
66
|
+
delete(bindingId: string): Promise<void>;
|
|
67
|
+
list(): Promise<SessionBindingRecord[]>;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
export interface
|
|
68
|
-
getTab(tabId: number): Promise<
|
|
69
|
-
getActiveTab(): Promise<
|
|
70
|
-
listTabs(filter?: { windowId?: number }): Promise<
|
|
71
|
-
createTab(options: { windowId?: number; url?: string; active?: boolean }): Promise<
|
|
72
|
-
updateTab(tabId: number, options: { active?: boolean; url?: string }): Promise<
|
|
70
|
+
export interface SessionBindingBrowser {
|
|
71
|
+
getTab(tabId: number): Promise<SessionBindingTab | null>;
|
|
72
|
+
getActiveTab(): Promise<SessionBindingTab | null>;
|
|
73
|
+
listTabs(filter?: { windowId?: number }): Promise<SessionBindingTab[]>;
|
|
74
|
+
createTab(options: { windowId?: number; url?: string; active?: boolean }): Promise<SessionBindingTab>;
|
|
75
|
+
updateTab(tabId: number, options: { active?: boolean; url?: string }): Promise<SessionBindingTab>;
|
|
73
76
|
closeTab(tabId: number): Promise<void>;
|
|
74
|
-
getWindow(windowId: number): Promise<
|
|
75
|
-
createWindow(options: { url?: string; focused?: boolean }): Promise<
|
|
76
|
-
updateWindow(windowId: number, options: { focused?: boolean }): Promise<
|
|
77
|
+
getWindow(windowId: number): Promise<SessionBindingWindow | null>;
|
|
78
|
+
createWindow(options: { url?: string; focused?: boolean }): Promise<SessionBindingWindow>;
|
|
79
|
+
updateWindow(windowId: number, options: { focused?: boolean }): Promise<SessionBindingWindow>;
|
|
77
80
|
closeWindow(windowId: number): Promise<void>;
|
|
78
|
-
getGroup(groupId: number): Promise<
|
|
81
|
+
getGroup(groupId: number): Promise<SessionBindingGroup | null>;
|
|
79
82
|
groupTabs(tabIds: number[], groupId?: number): Promise<number>;
|
|
80
|
-
updateGroup(groupId: number, options: { title?: string; color?:
|
|
83
|
+
updateGroup(groupId: number, options: { title?: string; color?: SessionBindingColor; collapsed?: boolean }): Promise<SessionBindingGroup>;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
interface
|
|
84
|
-
|
|
85
|
-
foreignTabs:
|
|
86
|
+
interface SessionBindingWindowOwnership {
|
|
87
|
+
bindingTabs: SessionBindingTab[];
|
|
88
|
+
foreignTabs: SessionBindingTab[];
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
export interface
|
|
89
|
-
|
|
91
|
+
export interface SessionBindingEnsureOptions {
|
|
92
|
+
bindingId?: string;
|
|
90
93
|
focus?: boolean;
|
|
91
94
|
initialUrl?: string;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
export interface
|
|
95
|
-
|
|
97
|
+
export interface SessionBindingOpenTabOptions {
|
|
98
|
+
bindingId?: string;
|
|
96
99
|
url?: string;
|
|
97
100
|
active?: boolean;
|
|
98
101
|
focus?: boolean;
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
export interface
|
|
104
|
+
export interface SessionBindingResolveTargetOptions {
|
|
102
105
|
tabId?: number;
|
|
103
|
-
|
|
106
|
+
bindingId?: string;
|
|
104
107
|
createIfMissing?: boolean;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
class SessionBindingManager {
|
|
108
|
-
private readonly storage:
|
|
109
|
-
private readonly browser:
|
|
111
|
+
private readonly storage: SessionBindingStorage;
|
|
112
|
+
private readonly browser: SessionBindingBrowser;
|
|
110
113
|
|
|
111
|
-
constructor(storage:
|
|
114
|
+
constructor(storage: SessionBindingStorage, browser: SessionBindingBrowser) {
|
|
112
115
|
this.storage = storage;
|
|
113
116
|
this.browser = browser;
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
async
|
|
117
|
-
return this.
|
|
119
|
+
async getBindingInfo(bindingId: string): Promise<SessionBindingInfo | null> {
|
|
120
|
+
return this.inspectBinding(bindingId);
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
async
|
|
121
|
-
const
|
|
123
|
+
async ensureBinding(options: SessionBindingEnsureOptions = {}): Promise<SessionBindingEnsureResult> {
|
|
124
|
+
const bindingId = this.normalizeBindingId(options.bindingId);
|
|
122
125
|
const repairActions: string[] = [];
|
|
123
|
-
const initialUrl = options.initialUrl ??
|
|
124
|
-
const persisted = await this.storage.load(
|
|
126
|
+
const initialUrl = options.initialUrl ?? DEFAULT_SESSION_BINDING_URL;
|
|
127
|
+
const persisted = await this.storage.load(bindingId);
|
|
125
128
|
const created = !persisted;
|
|
126
|
-
let state = this.normalizeState(persisted,
|
|
129
|
+
let state = this.normalizeState(persisted, bindingId);
|
|
127
130
|
|
|
128
131
|
const originalWindowId = state.windowId;
|
|
129
132
|
let window = state.windowId !== null ? await this.waitForWindow(state.windowId) : null;
|
|
130
|
-
let tabs:
|
|
133
|
+
let tabs: SessionBindingTab[] = [];
|
|
131
134
|
if (!window) {
|
|
132
|
-
const rebound = await this.
|
|
135
|
+
const rebound = await this.rebindBindingWindow(state);
|
|
133
136
|
if (rebound) {
|
|
134
137
|
window = rebound.window;
|
|
135
138
|
tabs = rebound.tabs;
|
|
@@ -161,7 +164,7 @@ class SessionBindingManager {
|
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
tabs = tabs.length > 0 ? tabs : await this.readTrackedTabs(state.tabIds, state.windowId);
|
|
164
|
-
const recoveredTabs = await this.
|
|
167
|
+
const recoveredTabs = await this.recoverBindingTabs(state, tabs);
|
|
165
168
|
if (recoveredTabs.length > tabs.length) {
|
|
166
169
|
tabs = recoveredTabs;
|
|
167
170
|
repairActions.push('recovered-tracked-tabs');
|
|
@@ -172,9 +175,9 @@ class SessionBindingManager {
|
|
|
172
175
|
state.tabIds = tabs.map((tab) => tab.id);
|
|
173
176
|
|
|
174
177
|
if (state.windowId !== null) {
|
|
175
|
-
const ownership = await this.
|
|
178
|
+
const ownership = await this.inspectBindingWindowOwnership(state, state.windowId);
|
|
176
179
|
if (ownership.foreignTabs.length > 0) {
|
|
177
|
-
const migrated = await this.
|
|
180
|
+
const migrated = await this.moveBindingIntoDedicatedWindow(state, ownership, initialUrl);
|
|
178
181
|
window = migrated.window;
|
|
179
182
|
tabs = migrated.tabs;
|
|
180
183
|
state.tabIds = tabs.map((tab) => tab.id);
|
|
@@ -183,7 +186,7 @@ class SessionBindingManager {
|
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
if (tabs.length === 0) {
|
|
186
|
-
const primary = await this.
|
|
189
|
+
const primary = await this.createBindingTab({
|
|
187
190
|
windowId: state.windowId,
|
|
188
191
|
url: initialUrl,
|
|
189
192
|
active: true
|
|
@@ -205,7 +208,7 @@ class SessionBindingManager {
|
|
|
205
208
|
repairActions.push('reassigned-active-tab');
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
let group = state.groupId !== null ? await this.
|
|
211
|
+
let group = state.groupId !== null ? await this.waitForGroup(state.groupId) : null;
|
|
209
212
|
if (!group || group.windowId !== state.windowId) {
|
|
210
213
|
const groupId = await this.browser.groupTabs(tabs.map((tab) => tab.id));
|
|
211
214
|
group = await this.browser.updateGroup(groupId, {
|
|
@@ -230,7 +233,7 @@ class SessionBindingManager {
|
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
tabs = await this.readTrackedTabs(state.tabIds, state.windowId);
|
|
233
|
-
tabs = await this.
|
|
236
|
+
tabs = await this.recoverBindingTabs(state, tabs);
|
|
234
237
|
const activeTab = state.activeTabId !== null ? await this.waitForTrackedTab(state.activeTabId, state.windowId) : null;
|
|
235
238
|
if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
|
|
236
239
|
tabs = [...tabs, activeTab];
|
|
@@ -253,7 +256,7 @@ class SessionBindingManager {
|
|
|
253
256
|
await this.storage.save(state);
|
|
254
257
|
|
|
255
258
|
return {
|
|
256
|
-
|
|
259
|
+
binding: {
|
|
257
260
|
...state,
|
|
258
261
|
tabs
|
|
259
262
|
},
|
|
@@ -263,17 +266,17 @@ class SessionBindingManager {
|
|
|
263
266
|
};
|
|
264
267
|
}
|
|
265
268
|
|
|
266
|
-
async openTab(options:
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const ensured = await this.
|
|
270
|
-
|
|
269
|
+
async openTab(options: SessionBindingOpenTabOptions = {}): Promise<{ binding: SessionBindingInfo; tab: SessionBindingTab }> {
|
|
270
|
+
const bindingId = this.normalizeBindingId(options.bindingId);
|
|
271
|
+
const hadBinding = (await this.loadBindingRecord(bindingId)) !== null;
|
|
272
|
+
const ensured = await this.ensureBinding({
|
|
273
|
+
bindingId,
|
|
271
274
|
focus: false,
|
|
272
|
-
initialUrl:
|
|
275
|
+
initialUrl: hadBinding ? options.url ?? DEFAULT_SESSION_BINDING_URL : DEFAULT_SESSION_BINDING_URL
|
|
273
276
|
});
|
|
274
|
-
let state = { ...ensured.
|
|
277
|
+
let state = { ...ensured.binding, tabIds: [...ensured.binding.tabIds], tabs: [...ensured.binding.tabs] };
|
|
275
278
|
if (state.windowId !== null && state.tabs.length === 0) {
|
|
276
|
-
const rebound = await this.
|
|
279
|
+
const rebound = await this.rebindBindingWindow(state);
|
|
277
280
|
if (rebound) {
|
|
278
281
|
state.windowId = rebound.window.id;
|
|
279
282
|
state.tabs = rebound.tabs;
|
|
@@ -281,7 +284,7 @@ class SessionBindingManager {
|
|
|
281
284
|
}
|
|
282
285
|
}
|
|
283
286
|
const active = options.active === true;
|
|
284
|
-
const desiredUrl = options.url ??
|
|
287
|
+
const desiredUrl = options.url ?? DEFAULT_SESSION_BINDING_URL;
|
|
285
288
|
let reusablePrimaryTab = await this.resolveReusablePrimaryTab(
|
|
286
289
|
state,
|
|
287
290
|
ensured.created ||
|
|
@@ -290,14 +293,14 @@ class SessionBindingManager {
|
|
|
290
293
|
ensured.repairActions.includes('migrated-dirty-window')
|
|
291
294
|
);
|
|
292
295
|
|
|
293
|
-
let createdTab:
|
|
296
|
+
let createdTab: SessionBindingTab;
|
|
294
297
|
try {
|
|
295
298
|
createdTab = reusablePrimaryTab
|
|
296
299
|
? await this.browser.updateTab(reusablePrimaryTab.id, {
|
|
297
300
|
url: desiredUrl,
|
|
298
301
|
active
|
|
299
302
|
})
|
|
300
|
-
: await this.
|
|
303
|
+
: await this.createBindingTab({
|
|
301
304
|
windowId: state.windowId,
|
|
302
305
|
url: desiredUrl,
|
|
303
306
|
active
|
|
@@ -306,19 +309,19 @@ class SessionBindingManager {
|
|
|
306
309
|
if (!this.isMissingWindowError(error)) {
|
|
307
310
|
throw error;
|
|
308
311
|
}
|
|
309
|
-
const repaired = await this.
|
|
310
|
-
|
|
312
|
+
const repaired = await this.ensureBinding({
|
|
313
|
+
bindingId,
|
|
311
314
|
focus: false,
|
|
312
315
|
initialUrl: desiredUrl
|
|
313
316
|
});
|
|
314
|
-
state = { ...repaired.
|
|
317
|
+
state = { ...repaired.binding };
|
|
315
318
|
reusablePrimaryTab = await this.resolveReusablePrimaryTab(state, true);
|
|
316
319
|
createdTab = reusablePrimaryTab
|
|
317
320
|
? await this.browser.updateTab(reusablePrimaryTab.id, {
|
|
318
321
|
url: desiredUrl,
|
|
319
322
|
active
|
|
320
323
|
})
|
|
321
|
-
: await this.
|
|
324
|
+
: await this.createBindingTab({
|
|
322
325
|
windowId: state.windowId,
|
|
323
326
|
url: desiredUrl,
|
|
324
327
|
active
|
|
@@ -331,14 +334,14 @@ class SessionBindingManager {
|
|
|
331
334
|
color: state.color,
|
|
332
335
|
collapsed: false
|
|
333
336
|
});
|
|
334
|
-
const nextState:
|
|
337
|
+
const nextState: SessionBindingRecord = {
|
|
335
338
|
id: state.id,
|
|
336
339
|
label: state.label,
|
|
337
340
|
color: state.color,
|
|
338
341
|
windowId: state.windowId,
|
|
339
342
|
groupId,
|
|
340
343
|
tabIds: nextTabIds,
|
|
341
|
-
activeTabId: createdTab.id,
|
|
344
|
+
activeTabId: active || options.focus === true ? createdTab.id : state.activeTabId ?? state.primaryTabId ?? createdTab.id,
|
|
342
345
|
primaryTabId: state.primaryTabId ?? createdTab.id
|
|
343
346
|
};
|
|
344
347
|
|
|
@@ -351,7 +354,7 @@ class SessionBindingManager {
|
|
|
351
354
|
const tabs = await this.readTrackedTabs(nextState.tabIds, nextState.windowId);
|
|
352
355
|
const tab = tabs.find((item) => item.id === createdTab.id) ?? createdTab;
|
|
353
356
|
return {
|
|
354
|
-
|
|
357
|
+
binding: {
|
|
355
358
|
...nextState,
|
|
356
359
|
tabs
|
|
357
360
|
},
|
|
@@ -359,58 +362,58 @@ class SessionBindingManager {
|
|
|
359
362
|
};
|
|
360
363
|
}
|
|
361
364
|
|
|
362
|
-
async listTabs(
|
|
363
|
-
const ensured = await this.
|
|
365
|
+
async listTabs(bindingId: string): Promise<{ binding: SessionBindingInfo; tabs: SessionBindingTab[] }> {
|
|
366
|
+
const ensured = await this.inspectBinding(bindingId);
|
|
364
367
|
if (!ensured) {
|
|
365
|
-
throw new Error(`
|
|
368
|
+
throw new Error(`Binding ${bindingId} does not exist`);
|
|
366
369
|
}
|
|
367
370
|
return {
|
|
368
|
-
|
|
371
|
+
binding: ensured,
|
|
369
372
|
tabs: ensured.tabs
|
|
370
373
|
};
|
|
371
374
|
}
|
|
372
375
|
|
|
373
|
-
async getActiveTab(
|
|
374
|
-
const ensured = await this.
|
|
376
|
+
async getActiveTab(bindingId: string): Promise<{ binding: SessionBindingInfo; tab: SessionBindingTab | null }> {
|
|
377
|
+
const ensured = await this.inspectBinding(bindingId);
|
|
375
378
|
if (!ensured) {
|
|
376
|
-
const
|
|
379
|
+
const normalizedBindingId = this.normalizeBindingId(bindingId);
|
|
377
380
|
return {
|
|
378
|
-
|
|
379
|
-
...this.normalizeState(null,
|
|
381
|
+
binding: {
|
|
382
|
+
...this.normalizeState(null, normalizedBindingId),
|
|
380
383
|
tabs: []
|
|
381
384
|
},
|
|
382
385
|
tab: null
|
|
383
386
|
};
|
|
384
387
|
}
|
|
385
388
|
return {
|
|
386
|
-
|
|
389
|
+
binding: ensured,
|
|
387
390
|
tab: ensured.tabs.find((tab) => tab.id === ensured.activeTabId) ?? null
|
|
388
391
|
};
|
|
389
392
|
}
|
|
390
393
|
|
|
391
|
-
async setActiveTab(tabId: number,
|
|
392
|
-
const ensured = await this.
|
|
393
|
-
if (!ensured.
|
|
394
|
-
throw new Error(`Tab ${tabId} does not belong to
|
|
394
|
+
async setActiveTab(tabId: number, bindingId: string): Promise<{ binding: SessionBindingInfo; tab: SessionBindingTab }> {
|
|
395
|
+
const ensured = await this.ensureBinding({ bindingId });
|
|
396
|
+
if (!ensured.binding.tabIds.includes(tabId)) {
|
|
397
|
+
throw new Error(`Tab ${tabId} does not belong to binding ${bindingId}`);
|
|
395
398
|
}
|
|
396
|
-
const nextState:
|
|
397
|
-
id: ensured.
|
|
398
|
-
label: ensured.
|
|
399
|
-
color: ensured.
|
|
400
|
-
windowId: ensured.
|
|
401
|
-
groupId: ensured.
|
|
402
|
-
tabIds: [...ensured.
|
|
399
|
+
const nextState: SessionBindingRecord = {
|
|
400
|
+
id: ensured.binding.id,
|
|
401
|
+
label: ensured.binding.label,
|
|
402
|
+
color: ensured.binding.color,
|
|
403
|
+
windowId: ensured.binding.windowId,
|
|
404
|
+
groupId: ensured.binding.groupId,
|
|
405
|
+
tabIds: [...ensured.binding.tabIds],
|
|
403
406
|
activeTabId: tabId,
|
|
404
|
-
primaryTabId: ensured.
|
|
407
|
+
primaryTabId: ensured.binding.primaryTabId ?? tabId
|
|
405
408
|
};
|
|
406
409
|
await this.storage.save(nextState);
|
|
407
410
|
const tabs = await this.readTrackedTabs(nextState.tabIds, nextState.windowId);
|
|
408
411
|
const tab = tabs.find((item) => item.id === tabId);
|
|
409
412
|
if (!tab) {
|
|
410
|
-
throw new Error(`Tab ${tabId} is missing from
|
|
413
|
+
throw new Error(`Tab ${tabId} is missing from binding ${bindingId}`);
|
|
411
414
|
}
|
|
412
415
|
return {
|
|
413
|
-
|
|
416
|
+
binding: {
|
|
414
417
|
...nextState,
|
|
415
418
|
tabs
|
|
416
419
|
},
|
|
@@ -418,36 +421,36 @@ class SessionBindingManager {
|
|
|
418
421
|
};
|
|
419
422
|
}
|
|
420
423
|
|
|
421
|
-
async focus(
|
|
422
|
-
const ensured = await this.
|
|
423
|
-
if (ensured.
|
|
424
|
-
await this.browser.updateTab(ensured.
|
|
424
|
+
async focus(bindingId: string): Promise<{ ok: true; binding: SessionBindingInfo }> {
|
|
425
|
+
const ensured = await this.ensureBinding({ bindingId, focus: false });
|
|
426
|
+
if (ensured.binding.activeTabId !== null) {
|
|
427
|
+
await this.browser.updateTab(ensured.binding.activeTabId, { active: true });
|
|
425
428
|
}
|
|
426
|
-
if (ensured.
|
|
427
|
-
await this.browser.updateWindow(ensured.
|
|
429
|
+
if (ensured.binding.windowId !== null) {
|
|
430
|
+
await this.browser.updateWindow(ensured.binding.windowId, { focused: true });
|
|
428
431
|
}
|
|
429
|
-
const refreshed = await this.
|
|
430
|
-
return { ok: true,
|
|
432
|
+
const refreshed = await this.ensureBinding({ bindingId, focus: false });
|
|
433
|
+
return { ok: true, binding: refreshed.binding };
|
|
431
434
|
}
|
|
432
435
|
|
|
433
|
-
async reset(options:
|
|
434
|
-
const
|
|
435
|
-
await this.close(
|
|
436
|
-
return this.
|
|
436
|
+
async reset(options: SessionBindingEnsureOptions = {}): Promise<SessionBindingEnsureResult> {
|
|
437
|
+
const bindingId = this.normalizeBindingId(options.bindingId);
|
|
438
|
+
await this.close(bindingId);
|
|
439
|
+
return this.ensureBinding({
|
|
437
440
|
...options,
|
|
438
|
-
|
|
441
|
+
bindingId
|
|
439
442
|
});
|
|
440
443
|
}
|
|
441
444
|
|
|
442
|
-
async close(
|
|
443
|
-
const state = await this.
|
|
445
|
+
async close(bindingId: string): Promise<{ ok: true }> {
|
|
446
|
+
const state = await this.loadBindingRecord(bindingId);
|
|
444
447
|
if (!state) {
|
|
445
|
-
await this.storage.delete(
|
|
448
|
+
await this.storage.delete(bindingId);
|
|
446
449
|
return { ok: true };
|
|
447
450
|
}
|
|
448
451
|
// Clear persisted state before closing the window so tab/window removal
|
|
449
|
-
// listeners cannot race and resurrect an empty
|
|
450
|
-
await this.storage.delete(
|
|
452
|
+
// listeners cannot race and resurrect an empty binding record.
|
|
453
|
+
await this.storage.delete(bindingId);
|
|
451
454
|
if (state.windowId !== null) {
|
|
452
455
|
const existingWindow = await this.browser.getWindow(state.windowId);
|
|
453
456
|
if (existingWindow) {
|
|
@@ -457,7 +460,7 @@ class SessionBindingManager {
|
|
|
457
460
|
return { ok: true };
|
|
458
461
|
}
|
|
459
462
|
|
|
460
|
-
async resolveTarget(options:
|
|
463
|
+
async resolveTarget(options: SessionBindingResolveTargetOptions = {}): Promise<SessionBindingTargetResolution> {
|
|
461
464
|
if (typeof options.tabId === 'number') {
|
|
462
465
|
const explicitTab = await this.browser.getTab(options.tabId);
|
|
463
466
|
if (!explicitTab) {
|
|
@@ -465,21 +468,21 @@ class SessionBindingManager {
|
|
|
465
468
|
}
|
|
466
469
|
return {
|
|
467
470
|
tab: explicitTab,
|
|
468
|
-
|
|
471
|
+
binding: null,
|
|
469
472
|
resolution: 'explicit-tab',
|
|
470
|
-
|
|
473
|
+
createdBinding: false,
|
|
471
474
|
repaired: false,
|
|
472
475
|
repairActions: []
|
|
473
476
|
};
|
|
474
477
|
}
|
|
475
478
|
|
|
476
|
-
const
|
|
477
|
-
if (
|
|
478
|
-
const ensured = await this.
|
|
479
|
-
|
|
479
|
+
const explicitBindingId = typeof options.bindingId === 'string' ? this.normalizeBindingId(options.bindingId) : undefined;
|
|
480
|
+
if (explicitBindingId) {
|
|
481
|
+
const ensured = await this.ensureBinding({
|
|
482
|
+
bindingId: explicitBindingId,
|
|
480
483
|
focus: false
|
|
481
484
|
});
|
|
482
|
-
return this.
|
|
485
|
+
return this.buildBindingResolution(ensured, 'explicit-binding');
|
|
483
486
|
}
|
|
484
487
|
|
|
485
488
|
if (options.createIfMissing !== true) {
|
|
@@ -489,30 +492,30 @@ class SessionBindingManager {
|
|
|
489
492
|
}
|
|
490
493
|
return {
|
|
491
494
|
tab: activeTab,
|
|
492
|
-
|
|
495
|
+
binding: null,
|
|
493
496
|
resolution: 'browser-active',
|
|
494
|
-
|
|
497
|
+
createdBinding: false,
|
|
495
498
|
repaired: false,
|
|
496
499
|
repairActions: []
|
|
497
500
|
};
|
|
498
501
|
}
|
|
499
502
|
|
|
500
|
-
throw new Error('
|
|
503
|
+
throw new Error('bindingId is required when createIfMissing is true');
|
|
501
504
|
}
|
|
502
505
|
|
|
503
|
-
private
|
|
504
|
-
const candidate =
|
|
506
|
+
private normalizeBindingId(bindingId?: string): string {
|
|
507
|
+
const candidate = bindingId?.trim();
|
|
505
508
|
if (!candidate) {
|
|
506
|
-
throw new Error('
|
|
509
|
+
throw new Error('bindingId is required');
|
|
507
510
|
}
|
|
508
511
|
return candidate;
|
|
509
512
|
}
|
|
510
513
|
|
|
511
|
-
private normalizeState(state:
|
|
514
|
+
private normalizeState(state: SessionBindingRecord | null, bindingId: string): SessionBindingRecord {
|
|
512
515
|
return {
|
|
513
|
-
id:
|
|
514
|
-
label: state?.label ??
|
|
515
|
-
color: state?.color ??
|
|
516
|
+
id: bindingId,
|
|
517
|
+
label: state?.label ?? DEFAULT_SESSION_BINDING_LABEL,
|
|
518
|
+
color: state?.color ?? DEFAULT_SESSION_BINDING_COLOR,
|
|
516
519
|
windowId: state?.windowId ?? null,
|
|
517
520
|
groupId: state?.groupId ?? null,
|
|
518
521
|
tabIds: state?.tabIds ?? [],
|
|
@@ -521,43 +524,43 @@ class SessionBindingManager {
|
|
|
521
524
|
};
|
|
522
525
|
}
|
|
523
526
|
|
|
524
|
-
async
|
|
527
|
+
async listBindingRecords(): Promise<SessionBindingRecord[]> {
|
|
525
528
|
return await this.storage.list();
|
|
526
529
|
}
|
|
527
530
|
|
|
528
|
-
private async
|
|
529
|
-
const
|
|
530
|
-
const state = await this.storage.load(
|
|
531
|
-
if (!state || state.id !==
|
|
531
|
+
private async loadBindingRecord(bindingId: string): Promise<SessionBindingRecord | null> {
|
|
532
|
+
const normalizedBindingId = this.normalizeBindingId(bindingId);
|
|
533
|
+
const state = await this.storage.load(normalizedBindingId);
|
|
534
|
+
if (!state || state.id !== normalizedBindingId) {
|
|
532
535
|
return null;
|
|
533
536
|
}
|
|
534
|
-
return this.normalizeState(state,
|
|
537
|
+
return this.normalizeState(state, normalizedBindingId);
|
|
535
538
|
}
|
|
536
539
|
|
|
537
|
-
private async
|
|
538
|
-
ensured:
|
|
539
|
-
resolution: 'explicit-
|
|
540
|
-
): Promise<
|
|
541
|
-
const tab = ensured.
|
|
540
|
+
private async buildBindingResolution(
|
|
541
|
+
ensured: SessionBindingEnsureResult,
|
|
542
|
+
resolution: 'explicit-binding' | 'default-binding'
|
|
543
|
+
): Promise<SessionBindingTargetResolution> {
|
|
544
|
+
const tab = ensured.binding.tabs.find((item) => item.id === ensured.binding.activeTabId) ?? ensured.binding.tabs[0] ?? null;
|
|
542
545
|
if (tab) {
|
|
543
546
|
return {
|
|
544
547
|
tab,
|
|
545
|
-
|
|
548
|
+
binding: ensured.binding,
|
|
546
549
|
resolution,
|
|
547
|
-
|
|
550
|
+
createdBinding: ensured.created,
|
|
548
551
|
repaired: ensured.repaired,
|
|
549
552
|
repairActions: ensured.repairActions
|
|
550
553
|
};
|
|
551
554
|
}
|
|
552
555
|
|
|
553
|
-
if (ensured.
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
556
|
+
if (ensured.binding.activeTabId !== null) {
|
|
557
|
+
const activeBindingTab = await this.waitForTrackedTab(ensured.binding.activeTabId, ensured.binding.windowId);
|
|
558
|
+
if (activeBindingTab) {
|
|
556
559
|
return {
|
|
557
|
-
tab:
|
|
558
|
-
|
|
560
|
+
tab: activeBindingTab,
|
|
561
|
+
binding: ensured.binding,
|
|
559
562
|
resolution,
|
|
560
|
-
|
|
563
|
+
createdBinding: ensured.created,
|
|
561
564
|
repaired: ensured.repaired,
|
|
562
565
|
repairActions: ensured.repairActions
|
|
563
566
|
};
|
|
@@ -570,15 +573,15 @@ class SessionBindingManager {
|
|
|
570
573
|
}
|
|
571
574
|
return {
|
|
572
575
|
tab: activeTab,
|
|
573
|
-
|
|
576
|
+
binding: null,
|
|
574
577
|
resolution: 'browser-active',
|
|
575
|
-
|
|
578
|
+
createdBinding: ensured.created,
|
|
576
579
|
repaired: ensured.repaired,
|
|
577
580
|
repairActions: ensured.repairActions
|
|
578
581
|
};
|
|
579
582
|
}
|
|
580
583
|
|
|
581
|
-
private async readTrackedTabs(tabIds: number[], windowId: number | null): Promise<
|
|
584
|
+
private async readTrackedTabs(tabIds: number[], windowId: number | null): Promise<SessionBindingTab[]> {
|
|
582
585
|
const tabs = (
|
|
583
586
|
await Promise.all(
|
|
584
587
|
tabIds.map(async (tabId) => {
|
|
@@ -592,26 +595,26 @@ class SessionBindingManager {
|
|
|
592
595
|
return tab;
|
|
593
596
|
})
|
|
594
597
|
)
|
|
595
|
-
).filter((tab): tab is
|
|
598
|
+
).filter((tab): tab is SessionBindingTab => tab !== null);
|
|
596
599
|
return tabs;
|
|
597
600
|
}
|
|
598
601
|
|
|
599
|
-
private async readLooseTrackedTabs(tabIds: number[]): Promise<
|
|
602
|
+
private async readLooseTrackedTabs(tabIds: number[]): Promise<SessionBindingTab[]> {
|
|
600
603
|
const tabs = (
|
|
601
604
|
await Promise.all(
|
|
602
605
|
tabIds.map(async (tabId) => {
|
|
603
606
|
return await this.browser.getTab(tabId);
|
|
604
607
|
})
|
|
605
608
|
)
|
|
606
|
-
).filter((tab): tab is
|
|
609
|
+
).filter((tab): tab is SessionBindingTab => tab !== null);
|
|
607
610
|
return tabs;
|
|
608
611
|
}
|
|
609
612
|
|
|
610
|
-
private collectCandidateTabIds(state:
|
|
613
|
+
private collectCandidateTabIds(state: SessionBindingRecord): number[] {
|
|
611
614
|
return [...new Set(state.tabIds.concat([state.activeTabId, state.primaryTabId].filter((value): value is number => typeof value === 'number')))];
|
|
612
615
|
}
|
|
613
616
|
|
|
614
|
-
private async
|
|
617
|
+
private async rebindBindingWindow(state: SessionBindingRecord): Promise<{ window: SessionBindingWindow; tabs: SessionBindingTab[] } | null> {
|
|
615
618
|
const candidateWindowIds: number[] = [];
|
|
616
619
|
const pushWindowId = (windowId: number | null | undefined): void => {
|
|
617
620
|
if (typeof windowId !== 'number') {
|
|
@@ -622,7 +625,7 @@ class SessionBindingManager {
|
|
|
622
625
|
}
|
|
623
626
|
};
|
|
624
627
|
|
|
625
|
-
const group = state.groupId !== null ? await this.
|
|
628
|
+
const group = state.groupId !== null ? await this.waitForGroup(state.groupId) : null;
|
|
626
629
|
pushWindowId(group?.windowId);
|
|
627
630
|
|
|
628
631
|
const trackedTabs = await this.readLooseTrackedTabs(this.collectCandidateTabIds(state));
|
|
@@ -631,13 +634,13 @@ class SessionBindingManager {
|
|
|
631
634
|
}
|
|
632
635
|
|
|
633
636
|
for (const candidateWindowId of candidateWindowIds) {
|
|
634
|
-
const window = await this.waitForWindow(candidateWindowId);
|
|
637
|
+
const window = await this.waitForWindow(candidateWindowId);
|
|
635
638
|
if (!window) {
|
|
636
639
|
continue;
|
|
637
640
|
}
|
|
638
641
|
let tabs = await this.readTrackedTabs(this.collectCandidateTabIds(state), candidateWindowId);
|
|
639
642
|
if (tabs.length === 0 && group?.id !== null && group?.windowId === candidateWindowId) {
|
|
640
|
-
const windowTabs = await this.waitForWindowTabs(candidateWindowId,
|
|
643
|
+
const windowTabs = await this.waitForWindowTabs(candidateWindowId, WINDOW_TABS_LOOKUP_TIMEOUT_MS);
|
|
641
644
|
tabs = windowTabs.filter((tab) => tab.groupId === group.id);
|
|
642
645
|
}
|
|
643
646
|
if (tabs.length === 0) {
|
|
@@ -659,27 +662,27 @@ class SessionBindingManager {
|
|
|
659
662
|
return null;
|
|
660
663
|
}
|
|
661
664
|
|
|
662
|
-
private async
|
|
665
|
+
private async inspectBindingWindowOwnership(state: SessionBindingRecord, windowId: number): Promise<SessionBindingWindowOwnership> {
|
|
663
666
|
const windowTabs = await this.waitForWindowTabs(windowId, 500);
|
|
664
667
|
const trackedIds = new Set(this.collectCandidateTabIds(state));
|
|
665
668
|
return {
|
|
666
|
-
|
|
669
|
+
bindingTabs: windowTabs.filter((tab) => trackedIds.has(tab.id) || (state.groupId !== null && tab.groupId === state.groupId)),
|
|
667
670
|
foreignTabs: windowTabs.filter((tab) => !trackedIds.has(tab.id) && (state.groupId === null || tab.groupId !== state.groupId))
|
|
668
671
|
};
|
|
669
672
|
}
|
|
670
673
|
|
|
671
|
-
private async
|
|
672
|
-
state:
|
|
673
|
-
ownership:
|
|
674
|
+
private async moveBindingIntoDedicatedWindow(
|
|
675
|
+
state: SessionBindingRecord,
|
|
676
|
+
ownership: SessionBindingWindowOwnership,
|
|
674
677
|
initialUrl: string
|
|
675
|
-
): Promise<{ window:
|
|
676
|
-
const sourceTabs = this.
|
|
678
|
+
): Promise<{ window: SessionBindingWindow; tabs: SessionBindingTab[] }> {
|
|
679
|
+
const sourceTabs = this.orderSessionBindingTabsForMigration(state, ownership.bindingTabs);
|
|
677
680
|
const seedUrl = sourceTabs[0]?.url ?? initialUrl;
|
|
678
681
|
const window = await this.browser.createWindow({
|
|
679
|
-
url: seedUrl ||
|
|
682
|
+
url: seedUrl || DEFAULT_SESSION_BINDING_URL,
|
|
680
683
|
focused: false
|
|
681
684
|
});
|
|
682
|
-
|
|
685
|
+
const recreatedTabs = await this.waitForWindowTabs(window.id);
|
|
683
686
|
const firstTab = recreatedTabs[0] ?? null;
|
|
684
687
|
const tabIdMap = new Map<number, number>();
|
|
685
688
|
if (sourceTabs[0] && firstTab) {
|
|
@@ -687,7 +690,7 @@ class SessionBindingManager {
|
|
|
687
690
|
}
|
|
688
691
|
|
|
689
692
|
for (const sourceTab of sourceTabs.slice(1)) {
|
|
690
|
-
const recreated = await this.
|
|
693
|
+
const recreated = await this.createBindingTab({
|
|
691
694
|
windowId: window.id,
|
|
692
695
|
url: sourceTab.url,
|
|
693
696
|
active: false
|
|
@@ -713,9 +716,9 @@ class SessionBindingManager {
|
|
|
713
716
|
state.primaryTabId = nextPrimaryTabId;
|
|
714
717
|
state.activeTabId = nextActiveTabId;
|
|
715
718
|
|
|
716
|
-
for (const
|
|
717
|
-
await this.browser.closeTab(
|
|
718
|
-
}
|
|
719
|
+
for (const bindingTab of ownership.bindingTabs) {
|
|
720
|
+
await this.browser.closeTab(bindingTab.id);
|
|
721
|
+
}
|
|
719
722
|
|
|
720
723
|
return {
|
|
721
724
|
window,
|
|
@@ -723,8 +726,8 @@ class SessionBindingManager {
|
|
|
723
726
|
};
|
|
724
727
|
}
|
|
725
728
|
|
|
726
|
-
private
|
|
727
|
-
const ordered:
|
|
729
|
+
private orderSessionBindingTabsForMigration(state: SessionBindingRecord, tabs: SessionBindingTab[]): SessionBindingTab[] {
|
|
730
|
+
const ordered: SessionBindingTab[] = [];
|
|
728
731
|
const seen = new Set<number>();
|
|
729
732
|
const pushById = (tabId: number | null): void => {
|
|
730
733
|
if (typeof tabId !== 'number') {
|
|
@@ -750,7 +753,7 @@ class SessionBindingManager {
|
|
|
750
753
|
return ordered;
|
|
751
754
|
}
|
|
752
755
|
|
|
753
|
-
private async
|
|
756
|
+
private async recoverBindingTabs(state: SessionBindingRecord, existingTabs: SessionBindingTab[]): Promise<SessionBindingTab[]> {
|
|
754
757
|
if (state.windowId === null) {
|
|
755
758
|
return existingTabs;
|
|
756
759
|
}
|
|
@@ -782,9 +785,9 @@ class SessionBindingManager {
|
|
|
782
785
|
return existingTabs;
|
|
783
786
|
}
|
|
784
787
|
|
|
785
|
-
private async
|
|
788
|
+
private async createBindingTab(options: { windowId: number | null; url: string; active: boolean }): Promise<SessionBindingTab> {
|
|
786
789
|
if (options.windowId === null) {
|
|
787
|
-
throw new Error('
|
|
790
|
+
throw new Error('Binding window is unavailable');
|
|
788
791
|
}
|
|
789
792
|
|
|
790
793
|
const deadline = Date.now() + 1_500;
|
|
@@ -809,8 +812,8 @@ class SessionBindingManager {
|
|
|
809
812
|
throw lastError ?? new Error(`No window with id: ${options.windowId}.`);
|
|
810
813
|
}
|
|
811
814
|
|
|
812
|
-
private async
|
|
813
|
-
const state = await this.
|
|
815
|
+
private async inspectBinding(bindingId: string): Promise<SessionBindingInfo | null> {
|
|
816
|
+
const state = await this.loadBindingRecord(bindingId);
|
|
814
817
|
if (!state) {
|
|
815
818
|
return null;
|
|
816
819
|
}
|
|
@@ -834,48 +837,60 @@ class SessionBindingManager {
|
|
|
834
837
|
};
|
|
835
838
|
}
|
|
836
839
|
|
|
837
|
-
private async resolveReusablePrimaryTab(
|
|
838
|
-
if (
|
|
840
|
+
private async resolveReusablePrimaryTab(binding: SessionBindingInfo, allowReuse: boolean): Promise<SessionBindingTab | null> {
|
|
841
|
+
if (binding.windowId === null) {
|
|
839
842
|
return null;
|
|
840
843
|
}
|
|
841
|
-
if (
|
|
842
|
-
const trackedPrimary =
|
|
843
|
-
if (trackedPrimary && (allowReuse || this.
|
|
844
|
+
if (binding.primaryTabId !== null) {
|
|
845
|
+
const trackedPrimary = binding.tabs.find((tab) => tab.id === binding.primaryTabId) ?? (await this.waitForTrackedTab(binding.primaryTabId, binding.windowId));
|
|
846
|
+
if (trackedPrimary && (allowReuse || this.isReusableBlankSessionBindingTab(trackedPrimary, binding))) {
|
|
844
847
|
return trackedPrimary;
|
|
845
848
|
}
|
|
846
849
|
}
|
|
847
|
-
const windowTabs = await this.waitForWindowTabs(
|
|
850
|
+
const windowTabs = await this.waitForWindowTabs(binding.windowId, WINDOW_TABS_LOOKUP_TIMEOUT_MS);
|
|
848
851
|
if (windowTabs.length !== 1) {
|
|
849
852
|
return null;
|
|
850
853
|
}
|
|
851
854
|
const candidate = windowTabs[0]!;
|
|
852
|
-
if (allowReuse || this.
|
|
855
|
+
if (allowReuse || this.isReusableBlankSessionBindingTab(candidate, binding)) {
|
|
853
856
|
return candidate;
|
|
854
857
|
}
|
|
855
858
|
return null;
|
|
856
859
|
}
|
|
857
860
|
|
|
858
|
-
private
|
|
859
|
-
if (
|
|
861
|
+
private isReusableBlankSessionBindingTab(tab: SessionBindingTab, binding: SessionBindingInfo): boolean {
|
|
862
|
+
if (binding.tabIds.length > 1) {
|
|
860
863
|
return false;
|
|
861
864
|
}
|
|
862
865
|
const normalizedUrl = tab.url.trim().toLowerCase();
|
|
863
|
-
return normalizedUrl === '' || normalizedUrl ===
|
|
866
|
+
return normalizedUrl === '' || normalizedUrl === DEFAULT_SESSION_BINDING_URL;
|
|
864
867
|
}
|
|
865
868
|
|
|
866
|
-
private async waitForWindow(windowId: number, timeoutMs =
|
|
867
|
-
const deadline = Date.now() + timeoutMs;
|
|
868
|
-
while (Date.now() < deadline) {
|
|
869
|
-
const window = await this.browser.getWindow(windowId);
|
|
870
|
-
if (window) {
|
|
871
|
-
return window;
|
|
869
|
+
private async waitForWindow(windowId: number, timeoutMs = WINDOW_LOOKUP_TIMEOUT_MS): Promise<SessionBindingWindow | null> {
|
|
870
|
+
const deadline = Date.now() + timeoutMs;
|
|
871
|
+
while (Date.now() < deadline) {
|
|
872
|
+
const window = await this.browser.getWindow(windowId);
|
|
873
|
+
if (window) {
|
|
874
|
+
return window;
|
|
872
875
|
}
|
|
873
876
|
await this.delay(50);
|
|
874
877
|
}
|
|
875
|
-
return null;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
private async
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
private async waitForGroup(groupId: number, timeoutMs = GROUP_LOOKUP_TIMEOUT_MS): Promise<SessionBindingGroup | null> {
|
|
882
|
+
const deadline = Date.now() + timeoutMs;
|
|
883
|
+
while (Date.now() < deadline) {
|
|
884
|
+
const group = await this.browser.getGroup(groupId);
|
|
885
|
+
if (group) {
|
|
886
|
+
return group;
|
|
887
|
+
}
|
|
888
|
+
await this.delay(50);
|
|
889
|
+
}
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
private async waitForTrackedTab(tabId: number, windowId: number | null, timeoutMs = 1_000): Promise<SessionBindingTab | null> {
|
|
879
894
|
const deadline = Date.now() + timeoutMs;
|
|
880
895
|
while (Date.now() < deadline) {
|
|
881
896
|
const tab = await this.browser.getTab(tabId);
|
|
@@ -887,7 +902,7 @@ class SessionBindingManager {
|
|
|
887
902
|
return null;
|
|
888
903
|
}
|
|
889
904
|
|
|
890
|
-
private async waitForWindowTabs(windowId: number, timeoutMs =
|
|
905
|
+
private async waitForWindowTabs(windowId: number, timeoutMs = WINDOW_TABS_LOOKUP_TIMEOUT_MS): Promise<SessionBindingTab[]> {
|
|
891
906
|
const deadline = Date.now() + timeoutMs;
|
|
892
907
|
while (Date.now() < deadline) {
|
|
893
908
|
const tabs = await this.browser.listTabs({ windowId });
|
|
@@ -909,4 +924,4 @@ class SessionBindingManager {
|
|
|
909
924
|
}
|
|
910
925
|
}
|
|
911
926
|
|
|
912
|
-
export { SessionBindingManager
|
|
927
|
+
export { SessionBindingManager };
|