btcp-browser-agent 0.1.12 → 0.1.16

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.
@@ -39,6 +39,7 @@
39
39
  * await client.click('@ref:5');
40
40
  * ```
41
41
  */
42
+ import { ChromeExtensionTransport } from './transport/chrome-extension.js';
42
43
  // Import for local use (and re-export below)
43
44
  import { BackgroundAgent as _BackgroundAgent, getBackgroundAgent as _getBackgroundAgent, setupMessageListener as _setupMessageListener, BrowserAgent as _BrowserAgent, getBrowserAgent as _getBrowserAgent, } from './background.js';
44
45
  export * from './types.js';
@@ -52,6 +53,8 @@ export { _BackgroundAgent as BackgroundAgent, _getBackgroundAgent as getBackgrou
52
53
  _BrowserAgent as BrowserAgent, _getBrowserAgent as getBrowserAgent, };
53
54
  // Re-export ContentAgent for content script usage
54
55
  export { createContentAgent } from '../../core/dist/index.js';
56
+ // Re-export transport module
57
+ export * from './transport/index.js';
55
58
  let commandIdCounter = 0;
56
59
  /**
57
60
  * Generate a unique command ID for BTCP commands
@@ -59,79 +62,41 @@ let commandIdCounter = 0;
59
62
  export function generateCommandId() {
60
63
  return `cmd_${Date.now()}_${commandIdCounter++}`;
61
64
  }
62
- /**
63
- * Check if we're running in a background/service worker context
64
- */
65
- function isBackgroundContext() {
66
- // In Manifest V3, background scripts run as service workers
67
- return typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope;
68
- }
69
65
  /**
70
66
  * Create a client for communicating with the extension
71
67
  *
72
- * This function works in both popup/content scripts and background scripts:
73
- * - In popup/content scripts: Uses chrome.runtime.sendMessage to communicate with background
74
- * - In background scripts: Uses BackgroundAgent directly for better performance
68
+ * By default uses ChromeExtensionTransport for popup/content script contexts.
69
+ * Pass a custom transport for different communication mechanisms.
75
70
  *
76
- * @example Popup usage:
71
+ * @example Default (Chrome Extension):
77
72
  * ```typescript
78
73
  * import { createClient } from '@btcp/browser-agent/extension';
79
74
  * const client = createClient();
80
75
  * await client.navigate('https://example.com');
81
76
  * ```
82
77
  *
83
- * @example Background script usage:
78
+ * @example With explicit transport:
84
79
  * ```typescript
85
- * import { createClient } from '@btcp/browser-agent/extension';
86
- * const client = createClient();
87
- * // Works the same way - commands go directly to BackgroundAgent
88
- * await client.navigate('https://example.com');
80
+ * import { createClient, createChromeExtensionTransport } from '@btcp/browser-agent/extension';
81
+ *
82
+ * const transport = createChromeExtensionTransport({ debug: true });
83
+ * const client = createClient({ transport });
84
+ * ```
85
+ *
86
+ * @example Direct transport (background script):
87
+ * ```typescript
88
+ * import { createClient, createDirectTransport, getBackgroundAgent } from '@btcp/browser-agent/extension';
89
+ *
90
+ * const transport = createDirectTransport({ agent: getBackgroundAgent() });
91
+ * const client = createClient({ transport });
89
92
  * ```
90
93
  */
91
- export function createClient() {
92
- // Detect if we're in background context
93
- const inBackground = isBackgroundContext();
94
- // Lazily get the background agent to avoid circular dependency issues
95
- let bgAgent = null;
96
- function getAgent() {
97
- if (!bgAgent) {
98
- // Use the singleton getter from background.js
99
- bgAgent = _getBackgroundAgent();
100
- }
101
- return bgAgent;
102
- }
94
+ export function createClient(options = {}) {
95
+ // Default to Chrome extension transport
96
+ const transport = options.transport ?? new ChromeExtensionTransport();
103
97
  async function sendCommand(command) {
104
- // In background context, use BackgroundAgent directly
105
- if (inBackground) {
106
- return getAgent().execute(command);
107
- }
108
- // In popup/content context, use message passing
109
98
  const id = command.id || generateCommandId();
110
- return new Promise((resolve) => {
111
- chrome.runtime.sendMessage({ type: 'btcp:command', command: { ...command, id } }, (response) => {
112
- if (chrome.runtime.lastError) {
113
- resolve({
114
- id,
115
- success: false,
116
- error: chrome.runtime.lastError.message || 'Unknown error',
117
- });
118
- }
119
- else {
120
- const resp = response;
121
- if (resp.type === 'btcp:response') {
122
- resolve(resp.response);
123
- }
124
- else {
125
- // Unexpected pong response
126
- resolve({
127
- id,
128
- success: false,
129
- error: 'Unexpected response type',
130
- });
131
- }
132
- }
133
- });
134
- });
99
+ return transport.send({ ...command, id });
135
100
  }
136
101
  function assertSuccess(response) {
137
102
  if (!response.success) {
@@ -190,65 +190,6 @@ export function createRemoteAgent(config) {
190
190
  });
191
191
  }
192
192
  }
193
- /**
194
- * Ensure a session exists, creating one if needed
195
- *
196
- * This checks in order:
197
- * 1. Current active session
198
- * 2. Persistent session from storage (reconnects if found)
199
- * 3. Existing BTCP tab groups (reconnects to first one found)
200
- * 4. Creates a new session if none found (respects maxSession limit)
201
- */
202
- async function ensureSession() {
203
- // 1. Check if there's an active session
204
- const sessionResult = await backgroundAgent.execute({ action: 'sessionGetCurrent' });
205
- if (sessionResult.success && sessionResult.data) {
206
- const session = sessionResult.data.session;
207
- if (session?.groupId) {
208
- log('Active session found:', session.groupId);
209
- return; // Session already exists
210
- }
211
- }
212
- // 2. Try to reconnect via popup initialize (handles persistent session check)
213
- log('No active session, trying to reconnect to existing session...');
214
- const initResult = await backgroundAgent.execute({ action: 'popupInitialize' });
215
- if (initResult.success && initResult.data) {
216
- const initData = initResult.data;
217
- if (initData.reconnected) {
218
- log('Reconnected to existing session');
219
- return;
220
- }
221
- }
222
- // 3. Check for existing BTCP tab groups and try to use one
223
- const groupsResult = await backgroundAgent.execute({ action: 'groupList' });
224
- if (groupsResult.success && groupsResult.data) {
225
- const groups = groupsResult.data;
226
- const btcpGroup = groups.find(g => g.title?.startsWith('BTCP'));
227
- if (btcpGroup) {
228
- log('Found existing BTCP tab group, setting it as active session:', btcpGroup.id);
229
- const useResult = await backgroundAgent.execute({
230
- action: 'sessionUseGroup',
231
- groupId: btcpGroup.id,
232
- });
233
- if (useResult.success) {
234
- log('Successfully using existing BTCP group as session');
235
- return;
236
- }
237
- log('Failed to use existing BTCP group:', useResult.error);
238
- }
239
- }
240
- // 4. Create a new session (will fail if maxSession limit reached)
241
- log('No existing session found, creating one automatically...');
242
- const groupResult = await backgroundAgent.execute({
243
- action: 'groupCreate',
244
- title: 'BTCP Session',
245
- color: 'blue',
246
- });
247
- if (!groupResult.success) {
248
- throw new Error(`Failed to create session: ${groupResult.error}`);
249
- }
250
- log('Session created:', groupResult.data);
251
- }
252
193
  /**
253
194
  * Handle incoming tool call request
254
195
  */
@@ -257,9 +198,7 @@ export function createRemoteAgent(config) {
257
198
  log('Tool call:', name, args);
258
199
  emit('toolCall', name, args);
259
200
  try {
260
- // Auto-ensure session for all browser tools (session management is internal)
261
- await ensureSession();
262
- // Map tool to command and execute
201
+ // Map tool to command and execute (session auto-ensured by BackgroundAgent)
263
202
  const command = mapToolToCommand(name, args);
264
203
  const response = await backgroundAgent.execute(command);
265
204
  // Send response back to server
@@ -24,13 +24,29 @@ export declare class SessionManager {
24
24
  private activeSessionGroupId;
25
25
  private sessionCounter;
26
26
  private initialized;
27
+ private initializationPromise;
27
28
  private maxSession;
28
29
  private maxOpenTab;
29
30
  constructor(options?: SessionManagerOptions);
31
+ /**
32
+ * Wait for SessionManager to finish initialization
33
+ */
34
+ waitForInitialization(): Promise<void>;
30
35
  /**
31
36
  * Restore session from storage
32
37
  */
33
38
  private restoreSession;
39
+ /**
40
+ * Scan for and cleanup duplicate BTCP session groups
41
+ * Ensures only one BTCP session exists at any time
42
+ *
43
+ * @returns Stats about cleanup: { found, kept, removed }
44
+ */
45
+ cleanupDuplicateSessions(): Promise<{
46
+ found: number;
47
+ kept: number;
48
+ removed: number;
49
+ }>;
34
50
  /**
35
51
  * Persist session to storage
36
52
  */
@@ -80,6 +96,10 @@ export declare class SessionManager {
80
96
  * Get the active session group ID
81
97
  */
82
98
  getActiveSessionGroupId(): number | null;
99
+ /**
100
+ * Get the active session group ID (async version that ensures initialization is complete)
101
+ */
102
+ getActiveSessionGroupIdAsync(): Promise<number | null>;
83
103
  /**
84
104
  * Get the maximum number of sessions allowed
85
105
  */
@@ -113,6 +133,16 @@ export declare class SessionManager {
113
133
  * This validates the group exists and sets it as active with persistence
114
134
  */
115
135
  useExistingGroupAsSession(groupId: number): Promise<boolean>;
136
+ /**
137
+ * Ensure a session exists - restore from storage, use existing, or create new
138
+ * Returns the session group ID (creates if needed)
139
+ */
140
+ ensureSession(): Promise<number>;
141
+ /**
142
+ * Get the primary tab in session (ensures session exists first)
143
+ * Returns the first tab in the session group
144
+ */
145
+ getSessionTab(): Promise<number>;
116
146
  /**
117
147
  * Add a tab to the active session (if one exists)
118
148
  * Automatically enforces the tab limit after adding
@@ -12,13 +12,20 @@ export class SessionManager {
12
12
  activeSessionGroupId = null;
13
13
  sessionCounter = 0;
14
14
  initialized = false;
15
+ initializationPromise;
15
16
  maxSession;
16
17
  maxOpenTab;
17
18
  constructor(options = {}) {
18
19
  this.maxSession = options.maxSession ?? 1;
19
20
  this.maxOpenTab = options.maxOpenTab ?? 1;
20
- // Restore session on creation
21
- this.restoreSession();
21
+ // Restore session on creation and store the promise
22
+ this.initializationPromise = this.restoreSession();
23
+ }
24
+ /**
25
+ * Wait for SessionManager to finish initialization
26
+ */
27
+ async waitForInitialization() {
28
+ await this.initializationPromise;
22
29
  }
23
30
  /**
24
31
  * Restore session from storage
@@ -49,6 +56,8 @@ export class SessionManager {
49
56
  else {
50
57
  console.log('[SessionManager] No stored session found');
51
58
  }
59
+ // Clean up any duplicate sessions after restoring
60
+ await this.cleanupDuplicateSessions();
52
61
  }
53
62
  catch (err) {
54
63
  console.error('[SessionManager] Failed to restore session:', err);
@@ -57,6 +66,98 @@ export class SessionManager {
57
66
  this.initialized = true;
58
67
  }
59
68
  }
69
+ /**
70
+ * Scan for and cleanup duplicate BTCP session groups
71
+ * Ensures only one BTCP session exists at any time
72
+ *
73
+ * @returns Stats about cleanup: { found, kept, removed }
74
+ */
75
+ async cleanupDuplicateSessions() {
76
+ try {
77
+ console.log('[SessionManager] Scanning for duplicate BTCP sessions...');
78
+ // Get all tab groups
79
+ const allGroups = await chrome.tabGroups.query({});
80
+ const btcpGroups = allGroups.filter(g => g.title?.startsWith('BTCP'));
81
+ console.log(`[SessionManager] Found ${btcpGroups.length} BTCP session group(s)`);
82
+ if (btcpGroups.length <= 1) {
83
+ // No duplicates, nothing to clean up
84
+ return { found: btcpGroups.length, kept: btcpGroups.length, removed: 0 };
85
+ }
86
+ // Multiple sessions found - need to consolidate
87
+ console.warn(`[SessionManager] Found ${btcpGroups.length} BTCP sessions - cleaning up duplicates!`);
88
+ // Determine which session to keep
89
+ let toKeep;
90
+ // Priority 1: Keep the one matching stored session ID
91
+ if (this.activeSessionGroupId !== null) {
92
+ const activeGroup = btcpGroups.find(g => g.id === this.activeSessionGroupId);
93
+ if (activeGroup) {
94
+ console.log('[SessionManager] Keeping active session:', activeGroup.id);
95
+ toKeep = activeGroup;
96
+ }
97
+ }
98
+ // Priority 2: Keep the one from storage if active session not set
99
+ if (!toKeep) {
100
+ const result = await chrome.storage.session.get(SESSION_STORAGE_KEY);
101
+ const data = result[SESSION_STORAGE_KEY];
102
+ if (data?.groupId) {
103
+ const storedGroup = btcpGroups.find(g => g.id === data.groupId);
104
+ if (storedGroup) {
105
+ console.log('[SessionManager] Keeping stored session:', storedGroup.id);
106
+ toKeep = storedGroup;
107
+ }
108
+ }
109
+ }
110
+ // Priority 3: Keep the one with the most tabs
111
+ if (!toKeep) {
112
+ console.log('[SessionManager] No active/stored session - keeping the one with most tabs');
113
+ const groupsWithTabCounts = await Promise.all(btcpGroups.map(async (g) => ({
114
+ group: g,
115
+ tabs: await chrome.tabs.query({ groupId: g.id })
116
+ })));
117
+ groupsWithTabCounts.sort((a, b) => b.tabs.length - a.tabs.length);
118
+ toKeep = groupsWithTabCounts[0]?.group;
119
+ if (toKeep) {
120
+ console.log(`[SessionManager] Keeping session ${toKeep.id} with ${groupsWithTabCounts[0].tabs.length} tabs`);
121
+ }
122
+ }
123
+ // If we still don't have a group to keep (shouldn't happen), just keep the first one
124
+ if (!toKeep) {
125
+ toKeep = btcpGroups[0];
126
+ console.log('[SessionManager] Fallback: keeping first session:', toKeep.id);
127
+ }
128
+ // Delete all other sessions
129
+ const toDelete = btcpGroups.filter(g => g.id !== toKeep.id);
130
+ console.log(`[SessionManager] Removing ${toDelete.length} duplicate session(s)`);
131
+ for (const group of toDelete) {
132
+ try {
133
+ console.log(`[SessionManager] Deleting duplicate session: ${group.id} (${group.title})`);
134
+ // Get tabs in this group and ungroup them (don't close them)
135
+ const tabs = await chrome.tabs.query({ groupId: group.id });
136
+ for (const tab of tabs) {
137
+ if (tab.id !== undefined) {
138
+ await chrome.tabs.ungroup(tab.id);
139
+ }
140
+ }
141
+ }
142
+ catch (err) {
143
+ console.error(`[SessionManager] Failed to delete duplicate session ${group.id}:`, err);
144
+ }
145
+ }
146
+ // Set the kept session as active
147
+ this.activeSessionGroupId = toKeep.id;
148
+ await this.persistSession();
149
+ console.log('[SessionManager] Cleanup complete - only one session remains');
150
+ return {
151
+ found: btcpGroups.length,
152
+ kept: 1,
153
+ removed: toDelete.length
154
+ };
155
+ }
156
+ catch (err) {
157
+ console.error('[SessionManager] Failed to cleanup duplicate sessions:', err);
158
+ return { found: 0, kept: 0, removed: 0 };
159
+ }
160
+ }
60
161
  /**
61
162
  * Persist session to storage
62
163
  */
@@ -95,6 +196,7 @@ export class SessionManager {
95
196
  * Used when popup detects a stored session that isn't currently active
96
197
  */
97
198
  async reconnectSession(groupId) {
199
+ await this.waitForInitialization();
98
200
  try {
99
201
  console.log('[SessionManager] Attempting to reconnect to session group:', groupId);
100
202
  // Verify the group still exists
@@ -106,6 +208,8 @@ export class SessionManager {
106
208
  // Restore session state
107
209
  this.activeSessionGroupId = groupId;
108
210
  this.sessionCounter = data?.sessionCounter ?? this.sessionCounter;
211
+ // Persist session state after reconnecting
212
+ await this.persistSession();
109
213
  console.log('[SessionManager] Session reconnected successfully');
110
214
  return true;
111
215
  }
@@ -120,6 +224,8 @@ export class SessionManager {
120
224
  * Create a new tab group
121
225
  */
122
226
  async createGroup(options = {}) {
227
+ // Wait for initialization to complete first
228
+ await this.waitForInitialization();
123
229
  console.log('[SessionManager] createGroup called with options:', options);
124
230
  // Check if we can create a new session
125
231
  const canCreate = await this.canCreateSession();
@@ -246,6 +352,7 @@ export class SessionManager {
246
352
  * Get current active session info
247
353
  */
248
354
  async getCurrentSession() {
355
+ await this.waitForInitialization();
249
356
  if (this.activeSessionGroupId === null) {
250
357
  return null;
251
358
  }
@@ -275,6 +382,13 @@ export class SessionManager {
275
382
  getActiveSessionGroupId() {
276
383
  return this.activeSessionGroupId;
277
384
  }
385
+ /**
386
+ * Get the active session group ID (async version that ensures initialization is complete)
387
+ */
388
+ async getActiveSessionGroupIdAsync() {
389
+ await this.waitForInitialization();
390
+ return this.activeSessionGroupId;
391
+ }
278
392
  /**
279
393
  * Get the maximum number of sessions allowed
280
394
  */
@@ -292,6 +406,7 @@ export class SessionManager {
292
406
  * Closes oldest tabs if the limit is exceeded
293
407
  */
294
408
  async enforceTabLimit() {
409
+ await this.waitForInitialization();
295
410
  if (this.activeSessionGroupId === null) {
296
411
  return;
297
412
  }
@@ -328,41 +443,31 @@ export class SessionManager {
328
443
  * 3. Existing tab groups (BTCP prefixed)
329
444
  */
330
445
  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
446
+ // Always count all BTCP tab groups to detect duplicates
344
447
  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;
448
+ const groups = await chrome.tabGroups.query({});
449
+ const btcpGroups = groups.filter(g => g.title?.startsWith('BTCP'));
450
+ // Clean up stale references while counting
451
+ if (this.activeSessionGroupId !== null) {
452
+ const activeExists = btcpGroups.some(g => g.id === this.activeSessionGroupId);
453
+ if (!activeExists) {
454
+ this.activeSessionGroupId = null;
352
455
  }
353
- catch {
354
- // Group no longer exists, clear storage
355
- await this.clearStoredSession();
456
+ }
457
+ // Check stored session exists
458
+ try {
459
+ const result = await chrome.storage.session.get(SESSION_STORAGE_KEY);
460
+ const data = result[SESSION_STORAGE_KEY];
461
+ if (data?.groupId) {
462
+ const storedExists = btcpGroups.some(g => g.id === data.groupId);
463
+ if (!storedExists) {
464
+ await this.clearStoredSession();
465
+ }
356
466
  }
357
467
  }
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'));
468
+ catch (err) {
469
+ console.error('[SessionManager] Failed to check persistent session:', err);
470
+ }
366
471
  return btcpGroups.length;
367
472
  }
368
473
  catch (err) {
@@ -404,11 +509,65 @@ export class SessionManager {
404
509
  return false;
405
510
  }
406
511
  }
512
+ /**
513
+ * Ensure a session exists - restore from storage, use existing, or create new
514
+ * Returns the session group ID (creates if needed)
515
+ */
516
+ async ensureSession() {
517
+ await this.waitForInitialization();
518
+ // Step 1: Already have active session
519
+ if (this.activeSessionGroupId !== null) {
520
+ // Verify it still exists
521
+ try {
522
+ await chrome.tabGroups.get(this.activeSessionGroupId);
523
+ return this.activeSessionGroupId;
524
+ }
525
+ catch {
526
+ // Group no longer exists, continue to restore/create
527
+ this.activeSessionGroupId = null;
528
+ }
529
+ }
530
+ // Step 2: Try to restore from storage
531
+ const result = await chrome.storage.session.get(SESSION_STORAGE_KEY);
532
+ const stored = result[SESSION_STORAGE_KEY];
533
+ if (stored?.groupId) {
534
+ const reconnected = await this.reconnectSession(stored.groupId);
535
+ if (reconnected && this.activeSessionGroupId !== null) {
536
+ return this.activeSessionGroupId;
537
+ }
538
+ }
539
+ // Step 3: Find existing BTCP group
540
+ const groups = await chrome.tabGroups.query({});
541
+ const btcpGroup = groups.find(g => g.title?.startsWith('BTCP'));
542
+ if (btcpGroup) {
543
+ const used = await this.useExistingGroupAsSession(btcpGroup.id);
544
+ if (used && this.activeSessionGroupId !== null) {
545
+ return this.activeSessionGroupId;
546
+ }
547
+ }
548
+ // Step 4: Create new session
549
+ console.log('[SessionManager] No existing session found, creating new one...');
550
+ const newGroup = await this.createGroup({ color: 'blue' });
551
+ return newGroup.id;
552
+ }
553
+ /**
554
+ * Get the primary tab in session (ensures session exists first)
555
+ * Returns the first tab in the session group
556
+ */
557
+ async getSessionTab() {
558
+ const groupId = await this.ensureSession();
559
+ const tabs = await chrome.tabs.query({ groupId });
560
+ if (tabs.length === 0 || tabs[0].id === undefined) {
561
+ throw new Error('Session exists but has no tabs');
562
+ }
563
+ return tabs[0].id;
564
+ }
407
565
  /**
408
566
  * Add a tab to the active session (if one exists)
409
567
  * Automatically enforces the tab limit after adding
410
568
  */
411
569
  async addTabToActiveSession(tabId) {
570
+ await this.waitForInitialization();
412
571
  if (this.activeSessionGroupId === null) {
413
572
  return false;
414
573
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Base transport class with shared functionality
3
+ *
4
+ * Provides event handling, state management, and utility methods for transports.
5
+ */
6
+ import type { Command, Response } from '../types.js';
7
+ import type { Transport, TransportEvents, TransportOptions, TransportState } from './types.js';
8
+ /**
9
+ * Abstract base class for transports
10
+ *
11
+ * Provides common functionality for event handling and state management.
12
+ * Subclasses must implement `send()`, `connect()`, and `disconnect()`.
13
+ */
14
+ export declare abstract class BaseTransport implements Transport {
15
+ abstract readonly name: string;
16
+ protected state: TransportState;
17
+ protected debug: boolean;
18
+ private eventHandlers;
19
+ constructor(options?: TransportOptions);
20
+ /**
21
+ * Send a command - must be implemented by subclasses
22
+ */
23
+ abstract send(command: Command): Promise<Response>;
24
+ /**
25
+ * Connect the transport - must be implemented by subclasses
26
+ */
27
+ abstract connect(): Promise<void>;
28
+ /**
29
+ * Disconnect the transport - must be implemented by subclasses
30
+ */
31
+ abstract disconnect(): void;
32
+ /**
33
+ * Get the current connection state
34
+ */
35
+ getState(): TransportState;
36
+ /**
37
+ * Check if the transport is connected
38
+ */
39
+ isConnected(): boolean;
40
+ /**
41
+ * Register an event handler
42
+ */
43
+ on<K extends keyof TransportEvents>(event: K, handler: TransportEvents[K]): void;
44
+ /**
45
+ * Unregister an event handler
46
+ */
47
+ off<K extends keyof TransportEvents>(event: K, handler: TransportEvents[K]): void;
48
+ /**
49
+ * Emit an event to all registered handlers
50
+ */
51
+ protected emit<K extends keyof TransportEvents>(event: K, ...args: Parameters<TransportEvents[K]>): void;
52
+ /**
53
+ * Update the transport state and emit stateChange event
54
+ */
55
+ protected setState(newState: TransportState): void;
56
+ /**
57
+ * Log a message if debug is enabled
58
+ */
59
+ protected log(level: 'debug' | 'info' | 'warn' | 'error', ...args: unknown[]): void;
60
+ /**
61
+ * Create an error response with the given message
62
+ */
63
+ protected createErrorResponse(id: string, error: string): Response;
64
+ }
65
+ //# sourceMappingURL=base-transport.d.ts.map