@flrande/bak-extension 0.6.8 → 0.6.10

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-13T08:51:15.417Z
1
+ 2026-03-13T17:21:34.586Z
@@ -62,7 +62,7 @@
62
62
  // package.json
63
63
  var package_default = {
64
64
  name: "@flrande/bak-extension",
65
- version: "0.6.8",
65
+ version: "0.6.10",
66
66
  type: "module",
67
67
  scripts: {
68
68
  build: "tsup src/background.ts src/content.ts src/popup.ts --format iife --out-dir dist --clean && node scripts/copy-assets.mjs",
@@ -562,6 +562,13 @@
562
562
  }
563
563
  }
564
564
  }
565
+ if (!window2) {
566
+ const activeWindow = await this.attachBindingToActiveWindow(state);
567
+ if (activeWindow) {
568
+ window2 = activeWindow;
569
+ repairActions.push("attached-active-window");
570
+ }
571
+ }
565
572
  if (!window2) {
566
573
  const sharedWindow = await this.findSharedBindingWindow(bindingId);
567
574
  if (sharedWindow) {
@@ -605,21 +612,11 @@
605
612
  repairActions.push("pruned-missing-tabs");
606
613
  }
607
614
  state.tabIds = tabs.map((tab) => tab.id);
608
- if (state.windowId !== null) {
609
- const ownership = await this.inspectBindingWindowOwnership(state, state.windowId);
610
- if (ownership.foreignTabs.length > 0 && ownership.bindingTabs.length > 0) {
611
- const migrated = await this.moveBindingIntoBakWindow(state, ownership, initialUrl);
612
- window2 = migrated.window;
613
- tabs = migrated.tabs;
614
- state.tabIds = tabs.map((tab) => tab.id);
615
- repairActions.push("evacuated-foreign-window");
616
- }
617
- }
618
615
  if (tabs.length === 0) {
619
616
  const primary = await this.createBindingTab({
620
617
  windowId: state.windowId,
621
618
  url: initialUrl,
622
- active: true
619
+ active: options.focus === true
623
620
  });
624
621
  tabs = [primary];
625
622
  state.tabIds = [primary.id];
@@ -670,9 +667,9 @@
670
667
  }
671
668
  }
672
669
  state.tabIds = [...new Set(tabs.map((tab) => tab.id))];
673
- if (options.focus === true && state.activeTabId !== null) {
674
- await this.browser.updateTab(state.activeTabId, { active: true });
675
- window2 = await this.browser.updateWindow(state.windowId, { focused: true });
670
+ if (options.focus === true && state.windowId !== null && state.activeTabId !== null) {
671
+ await this.focusBindingWindow(state.windowId, state.activeTabId);
672
+ window2 = await this.waitForWindow(state.windowId, 300);
676
673
  void window2;
677
674
  repairActions.push("focused-window");
678
675
  }
@@ -759,9 +756,8 @@
759
756
  activeTabId: active || options.focus === true ? createdTab.id : state.activeTabId ?? state.primaryTabId ?? createdTab.id,
760
757
  primaryTabId: state.primaryTabId ?? createdTab.id
761
758
  };
762
- if (options.focus === true) {
763
- await this.browser.updateTab(createdTab.id, { active: true });
764
- await this.browser.updateWindow(state.windowId, { focused: true });
759
+ if (options.focus === true && state.windowId !== null) {
760
+ await this.focusBindingWindow(state.windowId, createdTab.id);
765
761
  }
766
762
  await this.storage.save(nextState);
767
763
  const tabs = await this.readTrackedTabs(nextState.tabIds, nextState.windowId);
@@ -831,15 +827,41 @@
831
827
  };
832
828
  }
833
829
  async focus(bindingId) {
834
- const ensured = await this.ensureBinding({ bindingId, focus: false });
835
- if (ensured.binding.activeTabId !== null) {
836
- await this.browser.updateTab(ensured.binding.activeTabId, { active: true });
830
+ const normalizedBindingId = this.normalizeBindingId(bindingId);
831
+ let binding = await this.inspectBinding(normalizedBindingId) ?? (await this.ensureBinding({
832
+ bindingId: normalizedBindingId,
833
+ focus: false
834
+ })).binding;
835
+ let targetTabId = this.resolveFocusTabId(binding);
836
+ if (binding.windowId === null || targetTabId === null) {
837
+ binding = (await this.ensureBinding({
838
+ bindingId: normalizedBindingId,
839
+ focus: false
840
+ })).binding;
841
+ targetTabId = this.resolveFocusTabId(binding);
837
842
  }
838
- if (ensured.binding.windowId !== null) {
839
- await this.browser.updateWindow(ensured.binding.windowId, { focused: true });
843
+ if (binding.windowId !== null && targetTabId !== null) {
844
+ try {
845
+ await this.focusBindingWindow(binding.windowId, targetTabId);
846
+ } catch (error) {
847
+ if (!this.isMissingWindowError(error)) {
848
+ throw error;
849
+ }
850
+ binding = (await this.ensureBinding({
851
+ bindingId: normalizedBindingId,
852
+ focus: false
853
+ })).binding;
854
+ targetTabId = this.resolveFocusTabId(binding);
855
+ if (binding.windowId !== null && targetTabId !== null) {
856
+ await this.focusBindingWindow(binding.windowId, targetTabId);
857
+ }
858
+ }
840
859
  }
841
- const refreshed = await this.ensureBinding({ bindingId, focus: false });
842
- return { ok: true, binding: refreshed.binding };
860
+ const refreshed = await this.inspectBinding(normalizedBindingId) ?? binding;
861
+ return {
862
+ ok: true,
863
+ binding: refreshed.windowId !== null && targetTabId !== null ? this.withFocusedTab(refreshed, targetTabId) : refreshed
864
+ };
843
865
  }
844
866
  async closeTab(bindingId, tabId) {
845
867
  const binding = await this.inspectBinding(bindingId);
@@ -1081,121 +1103,21 @@
1081
1103
  }
1082
1104
  return null;
1083
1105
  }
1084
- async inspectBindingWindowOwnership(state, windowId) {
1085
- const windowTabs = await this.waitForWindowTabs(windowId, 500);
1086
- const bindingTabIds = new Set(this.collectCandidateTabIds(state));
1087
- const peerBindings = (await this.storage.list()).filter((candidate) => candidate.id !== state.id);
1088
- const peerTabIds = /* @__PURE__ */ new Set();
1089
- const peerGroupIds = /* @__PURE__ */ new Set();
1090
- for (const peer of peerBindings) {
1091
- for (const tabId of this.collectCandidateTabIds(peer)) {
1092
- peerTabIds.add(tabId);
1093
- }
1094
- if (peer.groupId !== null) {
1095
- peerGroupIds.add(peer.groupId);
1096
- }
1097
- }
1098
- const bindingTabs = [];
1099
- const sharedBindingTabs = [];
1100
- const foreignTabs = [];
1101
- for (const tab of windowTabs) {
1102
- if (bindingTabIds.has(tab.id) || state.groupId !== null && tab.groupId === state.groupId) {
1103
- bindingTabs.push(tab);
1104
- continue;
1105
- }
1106
- if (peerTabIds.has(tab.id) || tab.groupId !== null && peerGroupIds.has(tab.groupId)) {
1107
- sharedBindingTabs.push(tab);
1108
- continue;
1109
- }
1110
- foreignTabs.push(tab);
1111
- }
1112
- return {
1113
- bindingTabs,
1114
- sharedBindingTabs,
1115
- foreignTabs
1116
- };
1117
- }
1118
- async moveBindingIntoBakWindow(state, ownership, initialUrl) {
1119
- const sourceTabs = this.orderSessionBindingTabsForMigration(state, ownership.bindingTabs);
1120
- const seedUrl = sourceTabs[0]?.url ?? initialUrl;
1121
- const sharedWindow = await this.findSharedBindingWindow(state.id, state.windowId === null ? [] : [state.windowId]);
1122
- const window2 = sharedWindow ?? await this.browser.createWindow({
1123
- url: seedUrl || DEFAULT_SESSION_BINDING_URL,
1124
- focused: false
1125
- });
1126
- const initialTab = sharedWindow || typeof window2.initialTabId !== "number" ? null : await this.waitForTrackedTab(window2.initialTabId, window2.id);
1127
- const recreatedTabs = [];
1128
- const tabIdMap = /* @__PURE__ */ new Map();
1129
- if (sourceTabs[0]) {
1130
- const firstTab = initialTab ? await this.browser.updateTab(initialTab.id, {
1131
- url: sourceTabs[0].url,
1132
- active: false
1133
- }) : await this.createBindingTab({
1134
- windowId: window2.id,
1135
- url: sourceTabs[0].url,
1136
- active: false
1137
- });
1138
- recreatedTabs.push(firstTab);
1139
- tabIdMap.set(sourceTabs[0].id, firstTab.id);
1140
- }
1141
- for (const sourceTab of sourceTabs.slice(1)) {
1142
- const recreated = await this.createBindingTab({
1143
- windowId: window2.id,
1144
- url: sourceTab.url,
1145
- active: false
1146
- });
1147
- recreatedTabs.push(recreated);
1148
- tabIdMap.set(sourceTab.id, recreated.id);
1106
+ async attachBindingToActiveWindow(state) {
1107
+ const activeTab = await this.browser.getActiveTab();
1108
+ if (!activeTab) {
1109
+ return null;
1149
1110
  }
1150
- const nextPrimaryTabId = (state.primaryTabId !== null ? tabIdMap.get(state.primaryTabId) : void 0) ?? recreatedTabs[0]?.id ?? null;
1151
- const nextActiveTabId = (state.activeTabId !== null ? tabIdMap.get(state.activeTabId) : void 0) ?? nextPrimaryTabId ?? recreatedTabs[0]?.id ?? null;
1152
- if (nextActiveTabId !== null) {
1153
- await this.browser.updateTab(nextActiveTabId, { active: true });
1111
+ const window2 = await this.waitForWindow(activeTab.windowId, 300);
1112
+ if (!window2) {
1113
+ return null;
1154
1114
  }
1155
1115
  state.windowId = window2.id;
1156
1116
  state.groupId = null;
1157
- state.tabIds = recreatedTabs.map((tab) => tab.id);
1158
- state.primaryTabId = nextPrimaryTabId;
1159
- state.activeTabId = nextActiveTabId;
1160
- await this.storage.save({
1161
- ...state,
1162
- tabIds: [...state.tabIds]
1163
- });
1164
- for (const bindingTab of ownership.bindingTabs) {
1165
- try {
1166
- await this.browser.closeTab(bindingTab.id);
1167
- } catch {
1168
- }
1169
- }
1170
- return {
1171
- window: window2,
1172
- tabs: await this.readTrackedTabs(state.tabIds, state.windowId)
1173
- };
1174
- }
1175
- orderSessionBindingTabsForMigration(state, tabs) {
1176
- const ordered = [];
1177
- const seen = /* @__PURE__ */ new Set();
1178
- const pushById = (tabId) => {
1179
- if (typeof tabId !== "number") {
1180
- return;
1181
- }
1182
- const tab = tabs.find((candidate) => candidate.id === tabId);
1183
- if (!tab || seen.has(tab.id)) {
1184
- return;
1185
- }
1186
- ordered.push(tab);
1187
- seen.add(tab.id);
1188
- };
1189
- pushById(state.primaryTabId);
1190
- pushById(state.activeTabId);
1191
- for (const tab of tabs) {
1192
- if (seen.has(tab.id)) {
1193
- continue;
1194
- }
1195
- ordered.push(tab);
1196
- seen.add(tab.id);
1197
- }
1198
- return ordered;
1117
+ state.tabIds = [];
1118
+ state.activeTabId = null;
1119
+ state.primaryTabId = null;
1120
+ return window2;
1199
1121
  }
1200
1122
  async findSharedBindingWindow(bindingId, excludedWindowIds = []) {
1201
1123
  const peers = (await this.storage.list()).filter((candidate) => candidate.id !== bindingId);
@@ -1212,16 +1134,18 @@
1212
1134
  candidateWindowIds.push(windowId);
1213
1135
  };
1214
1136
  for (const peer of peers) {
1215
- pushWindowId(peer.windowId);
1216
- if (peer.groupId !== null) {
1217
- peerGroupIds.add(peer.groupId);
1218
- const group = await this.waitForGroup(peer.groupId, 300);
1219
- pushWindowId(group?.windowId);
1220
- }
1221
1137
  const trackedTabIds = this.collectCandidateTabIds(peer);
1138
+ pushWindowId(peer.windowId);
1222
1139
  for (const trackedTabId of trackedTabIds) {
1223
1140
  peerTabIds.add(trackedTabId);
1224
1141
  }
1142
+ if (peer.groupId !== null) {
1143
+ const group = await this.waitForGroup(peer.groupId, 300);
1144
+ if (group) {
1145
+ peerGroupIds.add(group.id);
1146
+ pushWindowId(group.windowId);
1147
+ }
1148
+ }
1225
1149
  const trackedTabs = await this.readLooseTrackedTabs(trackedTabIds);
1226
1150
  for (const tab of trackedTabs) {
1227
1151
  pushWindowId(tab.windowId);
@@ -1230,19 +1154,13 @@
1230
1154
  for (const windowId of candidateWindowIds) {
1231
1155
  const window2 = await this.waitForWindow(windowId, 300);
1232
1156
  if (window2) {
1233
- const windowTabs = await this.waitForWindowTabs(window2.id, 300);
1157
+ const windowTabs = await this.readStableWindowTabs(window2.id, WINDOW_TABS_LOOKUP_TIMEOUT_MS, 150);
1234
1158
  const ownedTabs = windowTabs.filter(
1235
1159
  (tab) => peerTabIds.has(tab.id) || tab.groupId !== null && peerGroupIds.has(tab.groupId)
1236
1160
  );
1237
1161
  if (ownedTabs.length === 0) {
1238
1162
  continue;
1239
1163
  }
1240
- const foreignTabs = windowTabs.filter(
1241
- (tab) => !peerTabIds.has(tab.id) && (tab.groupId === null || !peerGroupIds.has(tab.groupId))
1242
- );
1243
- if (foreignTabs.length > 0) {
1244
- continue;
1245
- }
1246
1164
  return window2;
1247
1165
  }
1248
1166
  }
@@ -1274,6 +1192,27 @@
1274
1192
  }
1275
1193
  return existingTabs;
1276
1194
  }
1195
+ resolveFocusTabId(binding) {
1196
+ return binding.activeTabId ?? binding.primaryTabId ?? binding.tabs[0]?.id ?? null;
1197
+ }
1198
+ withFocusedTab(binding, targetTabId) {
1199
+ return {
1200
+ ...binding,
1201
+ activeTabId: targetTabId,
1202
+ tabs: binding.tabs.map((tab) => ({
1203
+ ...tab,
1204
+ active: tab.id === targetTabId
1205
+ }))
1206
+ };
1207
+ }
1208
+ async focusBindingWindow(windowId, tabId) {
1209
+ await this.browser.updateWindow(windowId, { focused: true });
1210
+ await this.browser.updateTab(tabId, { active: true });
1211
+ const focusedTab = await this.waitForTrackedTab(tabId, windowId, 500);
1212
+ if (!focusedTab?.active) {
1213
+ await this.browser.updateTab(tabId, { active: true });
1214
+ }
1215
+ }
1277
1216
  async collectBindingTabsForClose(state) {
1278
1217
  const trackedTabs = await this.readLooseTrackedTabs(this.collectCandidateTabIds(state));
1279
1218
  if (state.windowId === null || state.groupId === null) {
@@ -1404,6 +1343,34 @@
1404
1343
  }
1405
1344
  return [];
1406
1345
  }
1346
+ async readStableWindowTabs(windowId, timeoutMs = WINDOW_TABS_LOOKUP_TIMEOUT_MS, settleMs = 150) {
1347
+ const deadline = Date.now() + timeoutMs;
1348
+ let bestTabs = [];
1349
+ let bestSignature = "";
1350
+ let lastSignature = "";
1351
+ let stableSince = 0;
1352
+ while (Date.now() < deadline) {
1353
+ const tabs = await this.browser.listTabs({ windowId });
1354
+ if (tabs.length > 0) {
1355
+ const signature = this.windowTabSnapshotSignature(tabs);
1356
+ if (tabs.length > bestTabs.length || tabs.length === bestTabs.length && signature !== bestSignature) {
1357
+ bestTabs = tabs;
1358
+ bestSignature = signature;
1359
+ }
1360
+ if (signature !== lastSignature) {
1361
+ lastSignature = signature;
1362
+ stableSince = Date.now();
1363
+ } else if (Date.now() - stableSince >= settleMs) {
1364
+ return bestTabs;
1365
+ }
1366
+ }
1367
+ await this.delay(50);
1368
+ }
1369
+ return bestTabs;
1370
+ }
1371
+ windowTabSnapshotSignature(tabs) {
1372
+ return [...tabs].map((tab) => `${tab.id}:${tab.groupId ?? "ungrouped"}`).sort().join("|");
1373
+ }
1407
1374
  async delay(ms) {
1408
1375
  await new Promise((resolve) => setTimeout(resolve, ms));
1409
1376
  }
@@ -1726,54 +1693,20 @@
1726
1693
  }
1727
1694
  },
1728
1695
  async createWindow(options) {
1729
- const previouslyFocusedWindow = options.focused === true ? null : (await chrome.windows.getAll()).find((window2) => window2.focused === true && typeof window2.id === "number") ?? null;
1730
- const previouslyFocusedTabs = previouslyFocusedWindow?.id !== void 0 ? await chrome.tabs.query({ windowId: previouslyFocusedWindow.id }) : [];
1731
- const previouslyFocusedTabIds = new Set(
1732
- previouslyFocusedTabs.flatMap((tab) => typeof tab.id === "number" ? [tab.id] : [])
1733
- );
1734
- const previouslyFocusedTab = previouslyFocusedTabs.find((tab) => tab.active === true && typeof tab.id === "number") ?? null;
1735
- const desiredUrl = options.url ?? "about:blank";
1736
- let created = await chrome.windows.create({
1737
- url: desiredUrl,
1738
- focused: true
1696
+ const created = await chrome.windows.create({
1697
+ focused: options.focused,
1698
+ url: options.url ?? "about:blank"
1739
1699
  });
1740
1700
  if (!created || typeof created.id !== "number") {
1741
1701
  throw new Error("Window missing id");
1742
1702
  }
1743
- const pickSeedTab = async (windowId) => {
1744
- const tabs = await chrome.tabs.query({ windowId });
1745
- const newlyCreatedTab = windowId === previouslyFocusedWindow?.id ? tabs.find((tab) => typeof tab.id === "number" && !previouslyFocusedTabIds.has(tab.id)) : null;
1746
- const normalizedDesiredUrl = normalizeComparableTabUrl(desiredUrl);
1747
- return newlyCreatedTab ?? tabs.find((tab) => {
1748
- const pendingUrl = "pendingUrl" in tab && typeof tab.pendingUrl === "string" ? tab.pendingUrl : "";
1749
- return normalizeComparableTabUrl(tab.url ?? pendingUrl) === normalizedDesiredUrl;
1750
- }) ?? tabs.find((tab) => tab.active === true && typeof tab.id === "number") ?? tabs.find((tab) => typeof tab.id === "number") ?? null;
1751
- };
1752
- let seedTab = await pickSeedTab(created.id);
1753
- const createdWindowTabs = await chrome.tabs.query({ windowId: created.id });
1754
- const createdWindowReusedFocusedWindow = previouslyFocusedWindow?.id === created.id;
1755
- const createdWindowLooksDirty = createdWindowTabs.length > 1;
1756
- if ((createdWindowReusedFocusedWindow || createdWindowLooksDirty) && typeof seedTab?.id === "number") {
1757
- created = await chrome.windows.create({
1758
- tabId: seedTab.id,
1759
- focused: true
1760
- });
1761
- if (!created || typeof created.id !== "number") {
1762
- throw new Error("Lifted window missing id");
1763
- }
1764
- seedTab = await pickSeedTab(created.id);
1765
- }
1766
- if (options.focused !== true && previouslyFocusedWindow?.id && previouslyFocusedWindow.id !== created.id) {
1767
- await chrome.windows.update(previouslyFocusedWindow.id, { focused: true });
1768
- if (typeof previouslyFocusedTab?.id === "number") {
1769
- await chrome.tabs.update(previouslyFocusedTab.id, { active: true });
1770
- }
1771
- }
1772
- const finalWindow = await chrome.windows.get(created.id);
1703
+ const initialTabId = created.tabs?.find((tab) => typeof tab.id === "number")?.id ?? (await chrome.tabs.query({
1704
+ windowId: created.id
1705
+ })).find((tab) => typeof tab.id === "number")?.id ?? null;
1773
1706
  return {
1774
- id: finalWindow.id,
1775
- focused: Boolean(finalWindow.focused),
1776
- initialTabId: seedTab?.id ?? null
1707
+ id: created.id,
1708
+ focused: Boolean(created.focused),
1709
+ initialTabId
1777
1710
  };
1778
1711
  },
1779
1712
  async updateWindow(windowId, options) {
@@ -2981,6 +2914,7 @@
2981
2914
  }
2982
2915
  case "sessionBinding.focus": {
2983
2916
  const result = await bindingManager.focus(String(params.bindingId ?? ""));
2917
+ emitSessionBindingUpdated(result.binding.id, "focus", result.binding);
2984
2918
  return {
2985
2919
  ok: true,
2986
2920
  browser: result.binding
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Browser Agent Kit",
4
- "version": "0.6.8",
4
+ "version": "0.6.10",
5
5
  "action": {
6
6
  "default_popup": "popup.html"
7
7
  },
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@flrande/bak-extension",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@flrande/bak-protocol": "0.6.8"
6
+ "@flrande/bak-protocol": "0.6.10"
7
7
  },
8
8
  "devDependencies": {
9
9
  "@types/chrome": "^0.1.14",