@flrande/bak-extension 0.3.1 → 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.
@@ -1 +1 @@
1
- 2026-03-09T04:26:20.181Z
1
+ 2026-03-09T04:50:59.948Z
@@ -52,8 +52,19 @@
52
52
  const persisted = await this.storage.load();
53
53
  const created = !persisted;
54
54
  let state = this.normalizeState(persisted, workspaceId);
55
+ const originalWindowId = state.windowId;
55
56
  let window = state.windowId !== null ? await this.waitForWindow(state.windowId) : null;
56
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
+ }
57
68
  if (!window) {
58
69
  const createdWindow = await this.browser.createWindow({
59
70
  url: initialUrl,
@@ -163,7 +174,15 @@
163
174
  focus: false,
164
175
  initialUrl: options.url ?? DEFAULT_WORKSPACE_URL
165
176
  });
166
- let state = { ...ensured.workspace };
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
+ }
167
186
  const active = options.active === true;
168
187
  const desiredUrl = options.url ?? DEFAULT_WORKSPACE_URL;
169
188
  let reusablePrimaryTab = await this.resolveReusablePrimaryTab(
@@ -452,6 +471,60 @@
452
471
  )).filter((tab) => tab !== null);
453
472
  return tabs;
454
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
+ }
455
528
  async recoverWorkspaceTabs(state, existingTabs) {
456
529
  if (state.windowId === null) {
457
530
  return existingTabs;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@flrande/bak-extension",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@flrande/bak-protocol": "0.3.1"
6
+ "@flrande/bak-protocol": "0.3.2"
7
7
  },
8
8
  "devDependencies": {
9
9
  "@types/chrome": "^0.1.14",
package/src/workspace.ts CHANGED
@@ -119,8 +119,19 @@ export class WorkspaceManager {
119
119
  const created = !persisted;
120
120
  let state = this.normalizeState(persisted, workspaceId);
121
121
 
122
+ const originalWindowId = state.windowId;
122
123
  let window = state.windowId !== null ? await this.waitForWindow(state.windowId) : null;
123
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
+ }
124
135
  if (!window) {
125
136
  const createdWindow = await this.browser.createWindow({
126
137
  url: initialUrl,
@@ -241,7 +252,15 @@ export class WorkspaceManager {
241
252
  focus: false,
242
253
  initialUrl: options.url ?? DEFAULT_WORKSPACE_URL
243
254
  });
244
- let state = { ...ensured.workspace };
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
+ }
245
264
  const active = options.active === true;
246
265
  const desiredUrl = options.url ?? DEFAULT_WORKSPACE_URL;
247
266
  let reusablePrimaryTab = await this.resolveReusablePrimaryTab(
@@ -561,6 +580,69 @@ export class WorkspaceManager {
561
580
  return tabs;
562
581
  }
563
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
+
564
646
  private async recoverWorkspaceTabs(state: WorkspaceRecord, existingTabs: WorkspaceTab[]): Promise<WorkspaceTab[]> {
565
647
  if (state.windowId === null) {
566
648
  return existingTabs;