btcp-browser-agent 0.1.9 → 0.1.11
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/LICENSE +21 -21
- package/README.md +338 -338
- package/package.json +69 -69
- package/packages/core/dist/actions.js +35 -35
- package/packages/extension/dist/background.js +21 -1
- package/packages/extension/dist/index.d.ts +12 -114
- package/packages/extension/dist/index.js +11 -121
- package/packages/extension/dist/remote.d.ts +3 -0
- package/packages/extension/dist/remote.js +76 -303
- package/packages/extension/dist/session-manager.d.ts +50 -2
- package/packages/extension/dist/session-manager.js +141 -3
- package/packages/extension/dist/session-types.d.ts +8 -1
- package/packages/extension/dist/types.d.ts +1 -1
|
@@ -12,7 +12,11 @@ export class SessionManager {
|
|
|
12
12
|
activeSessionGroupId = null;
|
|
13
13
|
sessionCounter = 0;
|
|
14
14
|
initialized = false;
|
|
15
|
-
|
|
15
|
+
maxSession;
|
|
16
|
+
maxOpenTab;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.maxSession = options.maxSession ?? 1;
|
|
19
|
+
this.maxOpenTab = options.maxOpenTab ?? 1;
|
|
16
20
|
// Restore session on creation
|
|
17
21
|
this.restoreSession();
|
|
18
22
|
}
|
|
@@ -117,6 +121,13 @@ export class SessionManager {
|
|
|
117
121
|
*/
|
|
118
122
|
async createGroup(options = {}) {
|
|
119
123
|
console.log('[SessionManager] createGroup called with options:', options);
|
|
124
|
+
// Check if we can create a new session
|
|
125
|
+
const canCreate = await this.canCreateSession();
|
|
126
|
+
if (!canCreate) {
|
|
127
|
+
const count = await this.getSessionCount();
|
|
128
|
+
throw new Error(`Maximum session limit reached (${count}/${this.maxSession}). ` +
|
|
129
|
+
`Close an existing session before creating a new one.`);
|
|
130
|
+
}
|
|
120
131
|
const { tabIds = [], title = this.generateSessionName(), color = 'blue', collapsed = false, } = options;
|
|
121
132
|
// If no tabIds provided, create a new blank tab for the session
|
|
122
133
|
let targetTabIds = tabIds;
|
|
@@ -264,14 +275,138 @@ export class SessionManager {
|
|
|
264
275
|
getActiveSessionGroupId() {
|
|
265
276
|
return this.activeSessionGroupId;
|
|
266
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Get the maximum number of sessions allowed
|
|
280
|
+
*/
|
|
281
|
+
getMaxSession() {
|
|
282
|
+
return this.maxSession;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get the maximum number of open tabs per session
|
|
286
|
+
*/
|
|
287
|
+
getMaxOpenTab() {
|
|
288
|
+
return this.maxOpenTab;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Enforce the tab limit in the active session
|
|
292
|
+
* Closes oldest tabs if the limit is exceeded
|
|
293
|
+
*/
|
|
294
|
+
async enforceTabLimit() {
|
|
295
|
+
if (this.activeSessionGroupId === null) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
// Get all tabs in the session
|
|
300
|
+
const tabs = await chrome.tabs.query({ groupId: this.activeSessionGroupId });
|
|
301
|
+
if (tabs.length <= this.maxOpenTab) {
|
|
302
|
+
return; // Within limit
|
|
303
|
+
}
|
|
304
|
+
// Sort by index (lower index = older tab position)
|
|
305
|
+
tabs.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
306
|
+
// Close excess tabs (oldest first)
|
|
307
|
+
const tabsToClose = tabs.slice(0, tabs.length - this.maxOpenTab);
|
|
308
|
+
console.log(`[SessionManager] Closing ${tabsToClose.length} excess tabs to enforce limit of ${this.maxOpenTab}`);
|
|
309
|
+
for (const tab of tabsToClose) {
|
|
310
|
+
if (tab.id) {
|
|
311
|
+
try {
|
|
312
|
+
await chrome.tabs.remove(tab.id);
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
console.error('[SessionManager] Failed to close tab:', err);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
console.error('[SessionManager] Failed to enforce tab limit:', err);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get the count of existing BTCP sessions by checking:
|
|
326
|
+
* 1. Persistent session from storage
|
|
327
|
+
* 2. Current active session
|
|
328
|
+
* 3. Existing tab groups (BTCP prefixed)
|
|
329
|
+
*/
|
|
330
|
+
async getSessionCount() {
|
|
331
|
+
// If we have an active session, that counts as 1
|
|
332
|
+
if (this.activeSessionGroupId !== null) {
|
|
333
|
+
try {
|
|
334
|
+
// Verify the group still exists
|
|
335
|
+
await chrome.tabGroups.get(this.activeSessionGroupId);
|
|
336
|
+
return 1;
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Group no longer exists, clear it
|
|
340
|
+
this.activeSessionGroupId = null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Check if there's a persistent session in storage
|
|
344
|
+
try {
|
|
345
|
+
const result = await chrome.storage.session.get(SESSION_STORAGE_KEY);
|
|
346
|
+
const data = result[SESSION_STORAGE_KEY];
|
|
347
|
+
if (data?.groupId) {
|
|
348
|
+
// Verify the stored group still exists
|
|
349
|
+
try {
|
|
350
|
+
await chrome.tabGroups.get(data.groupId);
|
|
351
|
+
return 1;
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
// Group no longer exists, clear storage
|
|
355
|
+
await this.clearStoredSession();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
console.error('[SessionManager] Failed to check persistent session:', err);
|
|
361
|
+
}
|
|
362
|
+
// Count existing BTCP tab groups
|
|
363
|
+
try {
|
|
364
|
+
const groups = await chrome.tabGroups.query({});
|
|
365
|
+
const btcpGroups = groups.filter(g => g.title?.startsWith('BTCP'));
|
|
366
|
+
return btcpGroups.length;
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
console.error('[SessionManager] Failed to count tab groups:', err);
|
|
370
|
+
return 0;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Check if a new session can be created based on maxSession limit
|
|
375
|
+
*/
|
|
376
|
+
async canCreateSession() {
|
|
377
|
+
const count = await this.getSessionCount();
|
|
378
|
+
return count < this.maxSession;
|
|
379
|
+
}
|
|
267
380
|
/**
|
|
268
381
|
* Set the active session group ID
|
|
269
382
|
*/
|
|
270
383
|
setActiveSessionGroupId(groupId) {
|
|
271
384
|
this.activeSessionGroupId = groupId;
|
|
272
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Use an existing tab group as the active session
|
|
388
|
+
* This validates the group exists and sets it as active with persistence
|
|
389
|
+
*/
|
|
390
|
+
async useExistingGroupAsSession(groupId) {
|
|
391
|
+
try {
|
|
392
|
+
// Verify the group exists
|
|
393
|
+
const group = await chrome.tabGroups.get(groupId);
|
|
394
|
+
console.log('[SessionManager] Using existing group as session:', group);
|
|
395
|
+
// Set as active session
|
|
396
|
+
this.activeSessionGroupId = groupId;
|
|
397
|
+
// Persist to storage
|
|
398
|
+
await this.persistSession();
|
|
399
|
+
console.log('[SessionManager] Existing group set as active session');
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
console.error('[SessionManager] Failed to use existing group as session:', err);
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
273
407
|
/**
|
|
274
408
|
* Add a tab to the active session (if one exists)
|
|
409
|
+
* Automatically enforces the tab limit after adding
|
|
275
410
|
*/
|
|
276
411
|
async addTabToActiveSession(tabId) {
|
|
277
412
|
if (this.activeSessionGroupId === null) {
|
|
@@ -279,6 +414,8 @@ export class SessionManager {
|
|
|
279
414
|
}
|
|
280
415
|
try {
|
|
281
416
|
await this.addTabsToGroup(this.activeSessionGroupId, [tabId]);
|
|
417
|
+
// Enforce tab limit after adding
|
|
418
|
+
await this.enforceTabLimit();
|
|
282
419
|
return true;
|
|
283
420
|
}
|
|
284
421
|
catch (error) {
|
|
@@ -311,10 +448,11 @@ export class SessionManager {
|
|
|
311
448
|
let sessionManagerInstance = null;
|
|
312
449
|
/**
|
|
313
450
|
* Get the singleton SessionManager instance
|
|
451
|
+
* @param options Options for the SessionManager (only used on first call)
|
|
314
452
|
*/
|
|
315
|
-
export function getSessionManager() {
|
|
453
|
+
export function getSessionManager(options) {
|
|
316
454
|
if (!sessionManagerInstance) {
|
|
317
|
-
sessionManagerInstance = new SessionManager();
|
|
455
|
+
sessionManagerInstance = new SessionManager(options);
|
|
318
456
|
}
|
|
319
457
|
return sessionManagerInstance;
|
|
320
458
|
}
|
|
@@ -106,8 +106,15 @@ export interface GroupGetCommand extends ExtensionBaseCommand {
|
|
|
106
106
|
export interface SessionGetCurrentCommand extends ExtensionBaseCommand {
|
|
107
107
|
action: 'sessionGetCurrent';
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Command to use an existing tab group as the active session
|
|
111
|
+
*/
|
|
112
|
+
export interface SessionUseGroupCommand extends ExtensionBaseCommand {
|
|
113
|
+
action: 'sessionUseGroup';
|
|
114
|
+
groupId: number;
|
|
115
|
+
}
|
|
109
116
|
/**
|
|
110
117
|
* Union type of all session-related commands
|
|
111
118
|
*/
|
|
112
|
-
export type SessionCommand = GroupCreateCommand | GroupUpdateCommand | GroupDeleteCommand | GroupListCommand | GroupAddTabsCommand | GroupRemoveTabsCommand | GroupGetCommand | SessionGetCurrentCommand;
|
|
119
|
+
export type SessionCommand = GroupCreateCommand | GroupUpdateCommand | GroupDeleteCommand | GroupListCommand | GroupAddTabsCommand | GroupRemoveTabsCommand | GroupGetCommand | SessionGetCurrentCommand | SessionUseGroupCommand;
|
|
113
120
|
//# sourceMappingURL=session-types.d.ts.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { Command as CoreCommand, Response } from '../../core/dist/index.js';
|
|
7
7
|
import type { SessionCommand } from './session-types.js';
|
|
8
|
-
export type ExtensionAction = 'navigate' | 'back' | 'forward' | 'reload' | 'getUrl' | 'getTitle' | 'screenshot' | 'tabNew' | 'tabClose' | 'tabSwitch' | 'tabList' | 'groupCreate' | 'groupUpdate' | 'groupDelete' | 'groupList' | 'groupAddTabs' | 'groupRemoveTabs' | 'groupGet' | 'sessionGetCurrent' | 'popupInitialize' | 'scriptInject' | 'scriptSend';
|
|
8
|
+
export type ExtensionAction = 'navigate' | 'back' | 'forward' | 'reload' | 'getUrl' | 'getTitle' | 'screenshot' | 'tabNew' | 'tabClose' | 'tabSwitch' | 'tabList' | 'groupCreate' | 'groupUpdate' | 'groupDelete' | 'groupList' | 'groupAddTabs' | 'groupRemoveTabs' | 'groupGet' | 'sessionGetCurrent' | 'sessionUseGroup' | 'popupInitialize' | 'scriptInject' | 'scriptSend';
|
|
9
9
|
export interface ExtensionBaseCommand {
|
|
10
10
|
/** Optional command ID. Auto-generated if not provided. */
|
|
11
11
|
id?: string;
|