@flrande/bak-extension 0.6.9 → 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-13T09:31:23.678Z
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.9",
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,128 +1103,27 @@
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);
1202
1124
  const candidateWindowIds = [];
1203
1125
  const peerTabIds = /* @__PURE__ */ new Set();
1204
1126
  const peerGroupIds = /* @__PURE__ */ new Set();
1205
- const reusablePeerIds = /* @__PURE__ */ new Set();
1206
1127
  const pushWindowId = (windowId) => {
1207
1128
  if (typeof windowId !== "number") {
1208
1129
  return;
@@ -1213,48 +1134,33 @@
1213
1134
  candidateWindowIds.push(windowId);
1214
1135
  };
1215
1136
  for (const peer of peers) {
1216
- if (peer.groupId === null) {
1217
- continue;
1218
- }
1219
- const group = await this.waitForGroup(peer.groupId, 300);
1220
- if (!group) {
1221
- continue;
1222
- }
1223
- reusablePeerIds.add(peer.id);
1224
- peerGroupIds.add(group.id);
1225
- pushWindowId(group.windowId);
1226
- }
1227
- for (const peer of peers) {
1228
- if (!reusablePeerIds.has(peer.id)) {
1229
- continue;
1230
- }
1231
1137
  const trackedTabIds = this.collectCandidateTabIds(peer);
1138
+ pushWindowId(peer.windowId);
1232
1139
  for (const trackedTabId of trackedTabIds) {
1233
1140
  peerTabIds.add(trackedTabId);
1234
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
+ }
1235
1149
  const trackedTabs = await this.readLooseTrackedTabs(trackedTabIds);
1236
1150
  for (const tab of trackedTabs) {
1237
- if (tab.groupId !== null && peerGroupIds.has(tab.groupId)) {
1238
- pushWindowId(tab.windowId);
1239
- }
1151
+ pushWindowId(tab.windowId);
1240
1152
  }
1241
1153
  }
1242
1154
  for (const windowId of candidateWindowIds) {
1243
1155
  const window2 = await this.waitForWindow(windowId, 300);
1244
1156
  if (window2) {
1245
- const windowTabs = await this.waitForWindowTabs(window2.id, 300);
1157
+ const windowTabs = await this.readStableWindowTabs(window2.id, WINDOW_TABS_LOOKUP_TIMEOUT_MS, 150);
1246
1158
  const ownedTabs = windowTabs.filter(
1247
1159
  (tab) => peerTabIds.has(tab.id) || tab.groupId !== null && peerGroupIds.has(tab.groupId)
1248
1160
  );
1249
1161
  if (ownedTabs.length === 0) {
1250
1162
  continue;
1251
1163
  }
1252
- const foreignTabs = windowTabs.filter(
1253
- (tab) => !peerTabIds.has(tab.id) && (tab.groupId === null || !peerGroupIds.has(tab.groupId))
1254
- );
1255
- if (foreignTabs.length > 0) {
1256
- continue;
1257
- }
1258
1164
  return window2;
1259
1165
  }
1260
1166
  }
@@ -1286,6 +1192,27 @@
1286
1192
  }
1287
1193
  return existingTabs;
1288
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
+ }
1289
1216
  async collectBindingTabsForClose(state) {
1290
1217
  const trackedTabs = await this.readLooseTrackedTabs(this.collectCandidateTabIds(state));
1291
1218
  if (state.windowId === null || state.groupId === null) {
@@ -1416,6 +1343,34 @@
1416
1343
  }
1417
1344
  return [];
1418
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
+ }
1419
1374
  async delay(ms) {
1420
1375
  await new Promise((resolve) => setTimeout(resolve, ms));
1421
1376
  }
@@ -1738,54 +1693,20 @@
1738
1693
  }
1739
1694
  },
1740
1695
  async createWindow(options) {
1741
- const previouslyFocusedWindow = options.focused === true ? null : (await chrome.windows.getAll()).find((window2) => window2.focused === true && typeof window2.id === "number") ?? null;
1742
- const previouslyFocusedTabs = previouslyFocusedWindow?.id !== void 0 ? await chrome.tabs.query({ windowId: previouslyFocusedWindow.id }) : [];
1743
- const previouslyFocusedTabIds = new Set(
1744
- previouslyFocusedTabs.flatMap((tab) => typeof tab.id === "number" ? [tab.id] : [])
1745
- );
1746
- const previouslyFocusedTab = previouslyFocusedTabs.find((tab) => tab.active === true && typeof tab.id === "number") ?? null;
1747
- const desiredUrl = options.url ?? "about:blank";
1748
- let created = await chrome.windows.create({
1749
- url: desiredUrl,
1750
- focused: true
1696
+ const created = await chrome.windows.create({
1697
+ focused: options.focused,
1698
+ url: options.url ?? "about:blank"
1751
1699
  });
1752
1700
  if (!created || typeof created.id !== "number") {
1753
1701
  throw new Error("Window missing id");
1754
1702
  }
1755
- const pickSeedTab = async (windowId) => {
1756
- const tabs = await chrome.tabs.query({ windowId });
1757
- const newlyCreatedTab = windowId === previouslyFocusedWindow?.id ? tabs.find((tab) => typeof tab.id === "number" && !previouslyFocusedTabIds.has(tab.id)) : null;
1758
- const normalizedDesiredUrl = normalizeComparableTabUrl(desiredUrl);
1759
- return newlyCreatedTab ?? tabs.find((tab) => {
1760
- const pendingUrl = "pendingUrl" in tab && typeof tab.pendingUrl === "string" ? tab.pendingUrl : "";
1761
- return normalizeComparableTabUrl(tab.url ?? pendingUrl) === normalizedDesiredUrl;
1762
- }) ?? tabs.find((tab) => tab.active === true && typeof tab.id === "number") ?? tabs.find((tab) => typeof tab.id === "number") ?? null;
1763
- };
1764
- let seedTab = await pickSeedTab(created.id);
1765
- const createdWindowTabs = await chrome.tabs.query({ windowId: created.id });
1766
- const createdWindowReusedFocusedWindow = previouslyFocusedWindow?.id === created.id;
1767
- const createdWindowLooksDirty = createdWindowTabs.length > 1;
1768
- if ((createdWindowReusedFocusedWindow || createdWindowLooksDirty) && typeof seedTab?.id === "number") {
1769
- created = await chrome.windows.create({
1770
- tabId: seedTab.id,
1771
- focused: true
1772
- });
1773
- if (!created || typeof created.id !== "number") {
1774
- throw new Error("Lifted window missing id");
1775
- }
1776
- seedTab = await pickSeedTab(created.id);
1777
- }
1778
- if (options.focused !== true && previouslyFocusedWindow?.id && previouslyFocusedWindow.id !== created.id) {
1779
- await chrome.windows.update(previouslyFocusedWindow.id, { focused: true });
1780
- if (typeof previouslyFocusedTab?.id === "number") {
1781
- await chrome.tabs.update(previouslyFocusedTab.id, { active: true });
1782
- }
1783
- }
1784
- 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;
1785
1706
  return {
1786
- id: finalWindow.id,
1787
- focused: Boolean(finalWindow.focused),
1788
- initialTabId: seedTab?.id ?? null
1707
+ id: created.id,
1708
+ focused: Boolean(created.focused),
1709
+ initialTabId
1789
1710
  };
1790
1711
  },
1791
1712
  async updateWindow(windowId, options) {
@@ -2993,6 +2914,7 @@
2993
2914
  }
2994
2915
  case "sessionBinding.focus": {
2995
2916
  const result = await bindingManager.focus(String(params.bindingId ?? ""));
2917
+ emitSessionBindingUpdated(result.binding.id, "focus", result.binding);
2996
2918
  return {
2997
2919
  ok: true,
2998
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.9",
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.9",
3
+ "version": "0.6.10",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@flrande/bak-protocol": "0.6.9"
6
+ "@flrande/bak-protocol": "0.6.10"
7
7
  },
8
8
  "devDependencies": {
9
9
  "@types/chrome": "^0.1.14",