@flrande/bak-extension 0.3.0 → 0.3.1

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.
@@ -1 +1 @@
1
- 2026-03-09T03:04:15.266Z
1
+ 2026-03-09T04:26:20.181Z
@@ -43,12 +43,7 @@
43
43
  this.browser = browser;
44
44
  }
45
45
  async getWorkspaceInfo(workspaceId = DEFAULT_WORKSPACE_ID) {
46
- const state = await this.loadWorkspaceRecord(workspaceId);
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,7 +52,7 @@
57
52
  const persisted = await this.storage.load();
58
53
  const created = !persisted;
59
54
  let state = this.normalizeState(persisted, workspaceId);
60
- let window = state.windowId !== null ? await this.browser.getWindow(state.windowId) : null;
55
+ let window = state.windowId !== null ? await this.waitForWindow(state.windowId) : null;
61
56
  let tabs = [];
62
57
  if (!window) {
63
58
  const createdWindow = await this.browser.createWindow({
@@ -138,6 +133,12 @@
138
133
  if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
139
134
  tabs = [...tabs, activeTab];
140
135
  }
136
+ if (tabs.length === 0 && state.primaryTabId !== null) {
137
+ const primaryTab = await this.waitForTrackedTab(state.primaryTabId, state.windowId);
138
+ if (primaryTab) {
139
+ tabs = [primaryTab];
140
+ }
141
+ }
141
142
  state.tabIds = [...new Set(tabs.map((tab) => tab.id))];
142
143
  if (options.focus === true && state.activeTabId !== null) {
143
144
  await this.browser.updateTab(state.activeTabId, { active: true });
@@ -162,18 +163,43 @@
162
163
  focus: false,
163
164
  initialUrl: options.url ?? DEFAULT_WORKSPACE_URL
164
165
  });
165
- const state = { ...ensured.workspace };
166
+ let state = { ...ensured.workspace };
166
167
  const active = options.active === true;
167
168
  const desiredUrl = options.url ?? DEFAULT_WORKSPACE_URL;
168
- const reusablePrimaryTab = (ensured.created || ensured.repairActions.includes("recreated-window") || ensured.repairActions.includes("created-primary-tab")) && state.tabs.length === 1 && state.primaryTabId !== null ? state.tabs.find((tab2) => tab2.id === state.primaryTabId) ?? null : null;
169
- const createdTab = reusablePrimaryTab ? await this.browser.updateTab(reusablePrimaryTab.id, {
170
- url: desiredUrl,
171
- active
172
- }) : await this.createWorkspaceTab({
173
- windowId: state.windowId,
174
- url: desiredUrl,
175
- active
176
- });
169
+ let reusablePrimaryTab = await this.resolveReusablePrimaryTab(
170
+ state,
171
+ ensured.created || ensured.repairActions.includes("recreated-window") || ensured.repairActions.includes("created-primary-tab")
172
+ );
173
+ let createdTab;
174
+ try {
175
+ createdTab = reusablePrimaryTab ? await this.browser.updateTab(reusablePrimaryTab.id, {
176
+ url: desiredUrl,
177
+ active
178
+ }) : await this.createWorkspaceTab({
179
+ windowId: state.windowId,
180
+ url: desiredUrl,
181
+ active
182
+ });
183
+ } catch (error) {
184
+ if (!this.isMissingWindowError(error)) {
185
+ throw error;
186
+ }
187
+ const repaired = await this.ensureWorkspace({
188
+ workspaceId: options.workspaceId,
189
+ focus: false,
190
+ initialUrl: desiredUrl
191
+ });
192
+ state = { ...repaired.workspace };
193
+ reusablePrimaryTab = await this.resolveReusablePrimaryTab(state, true);
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
+ }
177
203
  const nextTabIds = [.../* @__PURE__ */ new Set([...state.tabIds, createdTab.id])];
178
204
  const groupId = await this.browser.groupTabs([createdTab.id], state.groupId ?? void 0);
179
205
  await this.browser.updateGroup(groupId, {
@@ -207,17 +233,30 @@
207
233
  };
208
234
  }
209
235
  async listTabs(workspaceId = DEFAULT_WORKSPACE_ID) {
210
- const ensured = await this.ensureWorkspace({ workspaceId });
236
+ const ensured = await this.inspectWorkspace(workspaceId);
237
+ if (!ensured) {
238
+ throw new Error(`Workspace ${workspaceId} does not exist`);
239
+ }
211
240
  return {
212
- workspace: ensured.workspace,
213
- tabs: ensured.workspace.tabs
241
+ workspace: ensured,
242
+ tabs: ensured.tabs
214
243
  };
215
244
  }
216
245
  async getActiveTab(workspaceId = DEFAULT_WORKSPACE_ID) {
217
- const ensured = await this.ensureWorkspace({ workspaceId });
246
+ const ensured = await this.inspectWorkspace(workspaceId);
247
+ if (!ensured) {
248
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
249
+ return {
250
+ workspace: {
251
+ ...this.normalizeState(null, normalizedWorkspaceId),
252
+ tabs: []
253
+ },
254
+ tab: null
255
+ };
256
+ }
218
257
  return {
219
- workspace: ensured.workspace,
220
- tab: ensured.workspace.tabs.find((tab) => tab.id === ensured.workspace.activeTabId) ?? null
258
+ workspace: ensured,
259
+ tab: ensured.tabs.find((tab) => tab.id === ensured.activeTabId) ?? null
221
260
  };
222
261
  }
223
262
  async setActiveTab(tabId, workspaceId = DEFAULT_WORKSPACE_ID) {
@@ -446,12 +485,6 @@
446
485
  const deadline = Date.now() + 1500;
447
486
  let lastError2 = null;
448
487
  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
488
  try {
456
489
  return await this.browser.createTab({
457
490
  windowId: options.windowId,
@@ -468,6 +501,52 @@
468
501
  }
469
502
  throw lastError2 ?? new Error(`No window with id: ${options.windowId}.`);
470
503
  }
504
+ async inspectWorkspace(workspaceId = DEFAULT_WORKSPACE_ID) {
505
+ const state = await this.loadWorkspaceRecord(workspaceId);
506
+ if (!state) {
507
+ return null;
508
+ }
509
+ let tabs = await this.readTrackedTabs(state.tabIds, state.windowId);
510
+ const activeTab = state.activeTabId !== null ? await this.waitForTrackedTab(state.activeTabId, state.windowId, 300) : null;
511
+ if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
512
+ tabs = [...tabs, activeTab];
513
+ }
514
+ if (tabs.length === 0 && state.primaryTabId !== null) {
515
+ const primaryTab = await this.waitForTrackedTab(state.primaryTabId, state.windowId, 300);
516
+ if (primaryTab) {
517
+ tabs = [primaryTab];
518
+ }
519
+ }
520
+ return {
521
+ ...state,
522
+ tabIds: [...new Set(state.tabIds.concat(tabs.map((tab) => tab.id)))],
523
+ tabs
524
+ };
525
+ }
526
+ async resolveReusablePrimaryTab(workspace, allowReuse) {
527
+ if (!allowReuse || workspace.windowId === null) {
528
+ return null;
529
+ }
530
+ if (workspace.primaryTabId !== null) {
531
+ const trackedPrimary = workspace.tabs.find((tab) => tab.id === workspace.primaryTabId) ?? await this.waitForTrackedTab(workspace.primaryTabId, workspace.windowId);
532
+ if (trackedPrimary) {
533
+ return trackedPrimary;
534
+ }
535
+ }
536
+ const windowTabs = await this.waitForWindowTabs(workspace.windowId, 750);
537
+ return windowTabs.length === 1 ? windowTabs[0] : null;
538
+ }
539
+ async waitForWindow(windowId, timeoutMs = 750) {
540
+ const deadline = Date.now() + timeoutMs;
541
+ while (Date.now() < deadline) {
542
+ const window = await this.browser.getWindow(windowId);
543
+ if (window) {
544
+ return window;
545
+ }
546
+ await this.delay(50);
547
+ }
548
+ return null;
549
+ }
471
550
  async waitForTrackedTab(tabId, windowId, timeoutMs = 1e3) {
472
551
  const deadline = Date.now() + timeoutMs;
473
552
  while (Date.now() < deadline) {
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@flrande/bak-extension",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@flrande/bak-protocol": "0.3.0"
6
+ "@flrande/bak-protocol": "0.3.1"
7
7
  },
8
8
  "devDependencies": {
9
9
  "@types/chrome": "^0.1.14",
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
- const state = await this.loadWorkspaceRecord(workspaceId);
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,7 +119,7 @@ export class WorkspaceManager {
124
119
  const created = !persisted;
125
120
  let state = this.normalizeState(persisted, workspaceId);
126
121
 
127
- let window = state.windowId !== null ? await this.browser.getWindow(state.windowId) : null;
122
+ let window = state.windowId !== null ? await this.waitForWindow(state.windowId) : null;
128
123
  let tabs: WorkspaceTab[] = [];
129
124
  if (!window) {
130
125
  const createdWindow = await this.browser.createWindow({
@@ -212,6 +207,12 @@ export class WorkspaceManager {
212
207
  if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
213
208
  tabs = [...tabs, activeTab];
214
209
  }
210
+ if (tabs.length === 0 && state.primaryTabId !== null) {
211
+ const primaryTab = await this.waitForTrackedTab(state.primaryTabId, state.windowId);
212
+ if (primaryTab) {
213
+ tabs = [primaryTab];
214
+ }
215
+ }
215
216
  state.tabIds = [...new Set(tabs.map((tab) => tab.id))];
216
217
 
217
218
  if (options.focus === true && state.activeTabId !== null) {
@@ -240,26 +241,48 @@ export class WorkspaceManager {
240
241
  focus: false,
241
242
  initialUrl: options.url ?? DEFAULT_WORKSPACE_URL
242
243
  });
243
- const state = { ...ensured.workspace };
244
+ let state = { ...ensured.workspace };
244
245
  const active = options.active === true;
245
246
  const desiredUrl = options.url ?? DEFAULT_WORKSPACE_URL;
246
- const reusablePrimaryTab =
247
- (ensured.created || ensured.repairActions.includes('recreated-window') || ensured.repairActions.includes('created-primary-tab')) &&
248
- state.tabs.length === 1 &&
249
- state.primaryTabId !== null
250
- ? state.tabs.find((tab) => tab.id === state.primaryTabId) ?? null
251
- : null;
252
-
253
- const createdTab = reusablePrimaryTab
254
- ? await this.browser.updateTab(reusablePrimaryTab.id, {
255
- url: desiredUrl,
256
- active
257
- })
258
- : await this.createWorkspaceTab({
259
- windowId: state.windowId,
260
- url: desiredUrl,
261
- active
262
- });
247
+ let reusablePrimaryTab = await this.resolveReusablePrimaryTab(
248
+ state,
249
+ ensured.created || ensured.repairActions.includes('recreated-window') || ensured.repairActions.includes('created-primary-tab')
250
+ );
251
+
252
+ let createdTab: WorkspaceTab;
253
+ try {
254
+ createdTab = reusablePrimaryTab
255
+ ? await this.browser.updateTab(reusablePrimaryTab.id, {
256
+ url: desiredUrl,
257
+ active
258
+ })
259
+ : await this.createWorkspaceTab({
260
+ windowId: state.windowId,
261
+ url: desiredUrl,
262
+ active
263
+ });
264
+ } catch (error) {
265
+ if (!this.isMissingWindowError(error)) {
266
+ throw error;
267
+ }
268
+ const repaired = await this.ensureWorkspace({
269
+ workspaceId: options.workspaceId,
270
+ focus: false,
271
+ initialUrl: desiredUrl
272
+ });
273
+ state = { ...repaired.workspace };
274
+ reusablePrimaryTab = await this.resolveReusablePrimaryTab(state, true);
275
+ createdTab = reusablePrimaryTab
276
+ ? await this.browser.updateTab(reusablePrimaryTab.id, {
277
+ url: desiredUrl,
278
+ active
279
+ })
280
+ : await this.createWorkspaceTab({
281
+ windowId: state.windowId,
282
+ url: desiredUrl,
283
+ active
284
+ });
285
+ }
263
286
  const nextTabIds = [...new Set([...state.tabIds, createdTab.id])];
264
287
  const groupId = await this.browser.groupTabs([createdTab.id], state.groupId ?? undefined);
265
288
  await this.browser.updateGroup(groupId, {
@@ -296,18 +319,31 @@ export class WorkspaceManager {
296
319
  }
297
320
 
298
321
  async listTabs(workspaceId = DEFAULT_WORKSPACE_ID): Promise<{ workspace: WorkspaceInfo; tabs: WorkspaceTab[] }> {
299
- const ensured = await this.ensureWorkspace({ workspaceId });
322
+ const ensured = await this.inspectWorkspace(workspaceId);
323
+ if (!ensured) {
324
+ throw new Error(`Workspace ${workspaceId} does not exist`);
325
+ }
300
326
  return {
301
- workspace: ensured.workspace,
302
- tabs: ensured.workspace.tabs
327
+ workspace: ensured,
328
+ tabs: ensured.tabs
303
329
  };
304
330
  }
305
331
 
306
332
  async getActiveTab(workspaceId = DEFAULT_WORKSPACE_ID): Promise<{ workspace: WorkspaceInfo; tab: WorkspaceTab | null }> {
307
- const ensured = await this.ensureWorkspace({ workspaceId });
333
+ const ensured = await this.inspectWorkspace(workspaceId);
334
+ if (!ensured) {
335
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
336
+ return {
337
+ workspace: {
338
+ ...this.normalizeState(null, normalizedWorkspaceId),
339
+ tabs: []
340
+ },
341
+ tab: null
342
+ };
343
+ }
308
344
  return {
309
- workspace: ensured.workspace,
310
- tab: ensured.workspace.tabs.find((tab) => tab.id === ensured.workspace.activeTabId) ?? null
345
+ workspace: ensured,
346
+ tab: ensured.tabs.find((tab) => tab.id === ensured.activeTabId) ?? null
311
347
  };
312
348
  }
313
349
 
@@ -566,13 +602,6 @@ export class WorkspaceManager {
566
602
  let lastError: Error | null = null;
567
603
 
568
604
  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
605
  try {
577
606
  return await this.browser.createTab({
578
607
  windowId: options.windowId,
@@ -591,6 +620,57 @@ export class WorkspaceManager {
591
620
  throw lastError ?? new Error(`No window with id: ${options.windowId}.`);
592
621
  }
593
622
 
623
+ private async inspectWorkspace(workspaceId = DEFAULT_WORKSPACE_ID): Promise<WorkspaceInfo | null> {
624
+ const state = await this.loadWorkspaceRecord(workspaceId);
625
+ if (!state) {
626
+ return null;
627
+ }
628
+
629
+ let tabs = await this.readTrackedTabs(state.tabIds, state.windowId);
630
+ const activeTab = state.activeTabId !== null ? await this.waitForTrackedTab(state.activeTabId, state.windowId, 300) : null;
631
+ if (activeTab && !tabs.some((tab) => tab.id === activeTab.id)) {
632
+ tabs = [...tabs, activeTab];
633
+ }
634
+ if (tabs.length === 0 && state.primaryTabId !== null) {
635
+ const primaryTab = await this.waitForTrackedTab(state.primaryTabId, state.windowId, 300);
636
+ if (primaryTab) {
637
+ tabs = [primaryTab];
638
+ }
639
+ }
640
+
641
+ return {
642
+ ...state,
643
+ tabIds: [...new Set(state.tabIds.concat(tabs.map((tab) => tab.id)))],
644
+ tabs
645
+ };
646
+ }
647
+
648
+ private async resolveReusablePrimaryTab(workspace: WorkspaceInfo, allowReuse: boolean): Promise<WorkspaceTab | null> {
649
+ if (!allowReuse || workspace.windowId === null) {
650
+ return null;
651
+ }
652
+ if (workspace.primaryTabId !== null) {
653
+ const trackedPrimary = workspace.tabs.find((tab) => tab.id === workspace.primaryTabId) ?? (await this.waitForTrackedTab(workspace.primaryTabId, workspace.windowId));
654
+ if (trackedPrimary) {
655
+ return trackedPrimary;
656
+ }
657
+ }
658
+ const windowTabs = await this.waitForWindowTabs(workspace.windowId, 750);
659
+ return windowTabs.length === 1 ? windowTabs[0]! : null;
660
+ }
661
+
662
+ private async waitForWindow(windowId: number, timeoutMs = 750): Promise<WorkspaceWindow | null> {
663
+ const deadline = Date.now() + timeoutMs;
664
+ while (Date.now() < deadline) {
665
+ const window = await this.browser.getWindow(windowId);
666
+ if (window) {
667
+ return window;
668
+ }
669
+ await this.delay(50);
670
+ }
671
+ return null;
672
+ }
673
+
594
674
  private async waitForTrackedTab(tabId: number, windowId: number | null, timeoutMs = 1_000): Promise<WorkspaceTab | null> {
595
675
  const deadline = Date.now() + timeoutMs;
596
676
  while (Date.now() < deadline) {