@showrun/core 0.1.0

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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/dsl-validation.test.d.ts +2 -0
  3. package/dist/__tests__/dsl-validation.test.d.ts.map +1 -0
  4. package/dist/__tests__/dsl-validation.test.js +203 -0
  5. package/dist/__tests__/pack-versioning.test.d.ts +2 -0
  6. package/dist/__tests__/pack-versioning.test.d.ts.map +1 -0
  7. package/dist/__tests__/pack-versioning.test.js +165 -0
  8. package/dist/__tests__/validator.test.d.ts +2 -0
  9. package/dist/__tests__/validator.test.d.ts.map +1 -0
  10. package/dist/__tests__/validator.test.js +149 -0
  11. package/dist/authResilience.d.ts +146 -0
  12. package/dist/authResilience.d.ts.map +1 -0
  13. package/dist/authResilience.js +378 -0
  14. package/dist/browserLauncher.d.ts +74 -0
  15. package/dist/browserLauncher.d.ts.map +1 -0
  16. package/dist/browserLauncher.js +159 -0
  17. package/dist/browserPersistence.d.ts +49 -0
  18. package/dist/browserPersistence.d.ts.map +1 -0
  19. package/dist/browserPersistence.js +143 -0
  20. package/dist/context.d.ts +10 -0
  21. package/dist/context.d.ts.map +1 -0
  22. package/dist/context.js +30 -0
  23. package/dist/dsl/builders.d.ts +340 -0
  24. package/dist/dsl/builders.d.ts.map +1 -0
  25. package/dist/dsl/builders.js +416 -0
  26. package/dist/dsl/conditions.d.ts +33 -0
  27. package/dist/dsl/conditions.d.ts.map +1 -0
  28. package/dist/dsl/conditions.js +169 -0
  29. package/dist/dsl/interpreter.d.ts +24 -0
  30. package/dist/dsl/interpreter.d.ts.map +1 -0
  31. package/dist/dsl/interpreter.js +491 -0
  32. package/dist/dsl/stepHandlers.d.ts +32 -0
  33. package/dist/dsl/stepHandlers.d.ts.map +1 -0
  34. package/dist/dsl/stepHandlers.js +787 -0
  35. package/dist/dsl/target.d.ts +28 -0
  36. package/dist/dsl/target.d.ts.map +1 -0
  37. package/dist/dsl/target.js +110 -0
  38. package/dist/dsl/templating.d.ts +21 -0
  39. package/dist/dsl/templating.d.ts.map +1 -0
  40. package/dist/dsl/templating.js +73 -0
  41. package/dist/dsl/types.d.ts +695 -0
  42. package/dist/dsl/types.d.ts.map +1 -0
  43. package/dist/dsl/types.js +7 -0
  44. package/dist/dsl/validation.d.ts +15 -0
  45. package/dist/dsl/validation.d.ts.map +1 -0
  46. package/dist/dsl/validation.js +974 -0
  47. package/dist/index.d.ts +20 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +20 -0
  50. package/dist/jsonPackValidator.d.ts +11 -0
  51. package/dist/jsonPackValidator.d.ts.map +1 -0
  52. package/dist/jsonPackValidator.js +61 -0
  53. package/dist/loader.d.ts +35 -0
  54. package/dist/loader.d.ts.map +1 -0
  55. package/dist/loader.js +107 -0
  56. package/dist/networkCapture.d.ts +107 -0
  57. package/dist/networkCapture.d.ts.map +1 -0
  58. package/dist/networkCapture.js +390 -0
  59. package/dist/packUtils.d.ts +36 -0
  60. package/dist/packUtils.d.ts.map +1 -0
  61. package/dist/packUtils.js +97 -0
  62. package/dist/packVersioning.d.ts +25 -0
  63. package/dist/packVersioning.d.ts.map +1 -0
  64. package/dist/packVersioning.js +137 -0
  65. package/dist/runner.d.ts +62 -0
  66. package/dist/runner.d.ts.map +1 -0
  67. package/dist/runner.js +170 -0
  68. package/dist/types.d.ts +336 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +1 -0
  71. package/dist/validator.d.ts +20 -0
  72. package/dist/validator.d.ts.map +1 -0
  73. package/dist/validator.js +68 -0
  74. package/package.json +49 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Auth resilience module: handles "run once" steps, auth failure detection, and recovery
3
+ */
4
+ import type { Page } from 'playwright';
5
+ import type { DslStep } from './dsl/types.js';
6
+ import type { AuthPolicy, AuthGuard, Logger } from './types.js';
7
+ import type { NetworkEntrySerializable } from './networkCapture.js';
8
+ /**
9
+ * Outputs produced by a step (vars, collectibles, and network entries)
10
+ */
11
+ export interface StepOutput {
12
+ vars: Record<string, unknown>;
13
+ collectibles: Record<string, unknown>;
14
+ /** Network entries referenced by this step (for network_find/network_replay caching) */
15
+ networkEntries?: NetworkEntrySerializable[];
16
+ }
17
+ /**
18
+ * Cache for tracking executed "once" steps per session/profile.
19
+ * Stores step outputs (vars and collectibles) to restore them when steps are skipped.
20
+ * Can be loaded from and persisted to disk when sessionId/profileId are provided.
21
+ */
22
+ export declare class OnceCache {
23
+ private sessionCache;
24
+ private profileCache;
25
+ /**
26
+ * Create a cache, optionally loading from disk for the given sessionId and/or profileId.
27
+ *
28
+ * Storage locations:
29
+ * - Session cache: always in temp directory (ephemeral, cleared on server restart)
30
+ * - Profile cache: in profileCacheDir/.once-cache/profile.json if provided, else temp directory
31
+ */
32
+ static fromDisk(sessionId?: string, profileId?: string, profileCacheDir?: string): OnceCache;
33
+ /**
34
+ * Persist cache to disk for the given sessionId and/or profileId.
35
+ *
36
+ * Storage locations:
37
+ * - Session cache: always in temp directory (ephemeral)
38
+ * - Profile cache: in profileCacheDir/.once-cache/profile.json if provided, else temp directory
39
+ */
40
+ persist(sessionId?: string, profileId?: string, profileCacheDir?: string): void;
41
+ /**
42
+ * Check if a step should be skipped (already executed)
43
+ */
44
+ isExecuted(stepId: string, scope: 'session' | 'profile' | undefined): boolean;
45
+ /**
46
+ * Mark a step as executed with its outputs
47
+ */
48
+ markExecuted(stepId: string, scope: 'session' | 'profile', outputs?: StepOutput): void;
49
+ /**
50
+ * Get cached outputs for a step
51
+ */
52
+ getOutputs(stepId: string, scope: 'session' | 'profile'): StepOutput | undefined;
53
+ /**
54
+ * Clear cache for a specific scope
55
+ */
56
+ clear(scope: 'session' | 'profile'): void;
57
+ /**
58
+ * Clear all caches
59
+ */
60
+ clearAll(): void;
61
+ }
62
+ /**
63
+ * Auth failure detection result
64
+ */
65
+ export interface AuthFailure {
66
+ url: string;
67
+ status: number;
68
+ stepId?: string;
69
+ }
70
+ /**
71
+ * Auth failure monitor that watches network responses
72
+ */
73
+ export declare class AuthFailureMonitor {
74
+ private authPolicy;
75
+ private failures;
76
+ private urlRegex?;
77
+ constructor(authPolicy: AuthPolicy);
78
+ /**
79
+ * Check if auth policy is enabled
80
+ */
81
+ isEnabled(): boolean;
82
+ /**
83
+ * Check if a response indicates auth failure
84
+ */
85
+ isAuthFailure(url: string, status: number): boolean;
86
+ /**
87
+ * Record an auth failure
88
+ */
89
+ recordFailure(failure: AuthFailure): void;
90
+ /**
91
+ * Get the latest auth failure
92
+ */
93
+ getLatestFailure(): AuthFailure | undefined;
94
+ /**
95
+ * Get max recoveries allowed
96
+ */
97
+ getMaxRecoveries(): number;
98
+ /**
99
+ * Get max step retries after recovery
100
+ */
101
+ getMaxStepRetries(): number;
102
+ /**
103
+ * Get cooldown delay in ms
104
+ */
105
+ getCooldownMs(): number;
106
+ /**
107
+ * Clear recorded failures (for testing or reset)
108
+ */
109
+ clearFailures(): void;
110
+ /**
111
+ * Clear failures for a specific step ID
112
+ */
113
+ clearFailuresForStep(stepId: string): void;
114
+ /**
115
+ * Get failures for a specific step ID
116
+ */
117
+ getFailuresForStep(stepId: string): AuthFailure[];
118
+ }
119
+ /**
120
+ * Auth guard for proactive checks (OFF by default)
121
+ */
122
+ export declare class AuthGuardChecker {
123
+ private guard;
124
+ constructor(guard: AuthGuard);
125
+ /**
126
+ * Check if guard is enabled
127
+ */
128
+ isEnabled(): boolean;
129
+ /**
130
+ * Check auth status using configured strategy
131
+ */
132
+ checkAuth(page: Page): Promise<boolean>;
133
+ }
134
+ /**
135
+ * Setup network response monitoring for browser sessions
136
+ */
137
+ export declare function setupBrowserAuthMonitoring(page: Page, monitor: AuthFailureMonitor, logger: Logger, currentStepId?: string): void;
138
+ /**
139
+ * Check if a step should be skipped due to "once" cache
140
+ */
141
+ export declare function shouldSkipStep(step: DslStep, onceCache: OnceCache, sessionId?: string, profileId?: string): boolean;
142
+ /**
143
+ * Get steps that should be rerun during recovery (all steps with once flag)
144
+ */
145
+ export declare function getOnceSteps(steps: DslStep[]): DslStep[];
146
+ //# sourceMappingURL=authResilience.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authResilience.d.ts","sourceRoot":"","sources":["../src/authResilience.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Auth resilience module: handles "run once" steps, auth failure detection, and recovery
3
+ */
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+ const ONCE_CACHE_DIR_ENV = 'SHOWRUN_ONCE_CACHE_DIR';
8
+ function getOnceCacheDir() {
9
+ const dir = process.env[ONCE_CACHE_DIR_ENV] ?? join(tmpdir(), 'showrun-once-cache');
10
+ return dir;
11
+ }
12
+ /** Sanitize id for use in cache filename (no path separators) */
13
+ function sanitizeCacheId(id) {
14
+ return id.replace(/[/\\]/g, '-');
15
+ }
16
+ /**
17
+ * Cache for tracking executed "once" steps per session/profile.
18
+ * Stores step outputs (vars and collectibles) to restore them when steps are skipped.
19
+ * Can be loaded from and persisted to disk when sessionId/profileId are provided.
20
+ */
21
+ export class OnceCache {
22
+ sessionCache = new Map();
23
+ profileCache = new Map();
24
+ /**
25
+ * Create a cache, optionally loading from disk for the given sessionId and/or profileId.
26
+ *
27
+ * Storage locations:
28
+ * - Session cache: always in temp directory (ephemeral, cleared on server restart)
29
+ * - Profile cache: in profileCacheDir/.once-cache/profile.json if provided, else temp directory
30
+ */
31
+ static fromDisk(sessionId, profileId, profileCacheDir) {
32
+ const defaultDir = getOnceCacheDir();
33
+ const cache = new OnceCache();
34
+ // Session cache always in temp directory (ephemeral)
35
+ if (sessionId) {
36
+ const file = join(defaultDir, `once-session-${sanitizeCacheId(sessionId)}.json`);
37
+ if (existsSync(file)) {
38
+ try {
39
+ const data = readFileSync(file, 'utf-8');
40
+ const parsed = JSON.parse(data);
41
+ // Backward compatibility: if data is an array (old format), convert to Map with empty outputs
42
+ if (Array.isArray(parsed)) {
43
+ parsed.forEach((id) => cache.sessionCache.set(id, { vars: {}, collectibles: {} }));
44
+ }
45
+ else if (typeof parsed === 'object' && parsed !== null) {
46
+ // New format: object with stepId -> StepOutput
47
+ for (const [stepId, outputs] of Object.entries(parsed)) {
48
+ const stepOutput = outputs;
49
+ cache.sessionCache.set(stepId, {
50
+ vars: stepOutput.vars ?? {},
51
+ collectibles: stepOutput.collectibles ?? {},
52
+ networkEntries: stepOutput.networkEntries,
53
+ });
54
+ }
55
+ }
56
+ }
57
+ catch {
58
+ // Ignore corrupt or missing file
59
+ }
60
+ }
61
+ }
62
+ // Profile cache in pack directory if provided, else default temp dir
63
+ if (profileId) {
64
+ let profileFile;
65
+ if (profileCacheDir) {
66
+ const cacheSubdir = join(profileCacheDir, '.once-cache');
67
+ profileFile = join(cacheSubdir, 'profile.json');
68
+ }
69
+ else {
70
+ profileFile = join(defaultDir, `once-profile-${sanitizeCacheId(profileId)}.json`);
71
+ }
72
+ if (existsSync(profileFile)) {
73
+ try {
74
+ const data = readFileSync(profileFile, 'utf-8');
75
+ const parsed = JSON.parse(data);
76
+ // Backward compatibility: if data is an array (old format), convert to Map with empty outputs
77
+ if (Array.isArray(parsed)) {
78
+ parsed.forEach((id) => cache.profileCache.set(id, { vars: {}, collectibles: {} }));
79
+ }
80
+ else if (typeof parsed === 'object' && parsed !== null) {
81
+ // New format: object with stepId -> StepOutput
82
+ for (const [stepId, outputs] of Object.entries(parsed)) {
83
+ const stepOutput = outputs;
84
+ cache.profileCache.set(stepId, {
85
+ vars: stepOutput.vars ?? {},
86
+ collectibles: stepOutput.collectibles ?? {},
87
+ networkEntries: stepOutput.networkEntries,
88
+ });
89
+ }
90
+ }
91
+ }
92
+ catch {
93
+ // Ignore corrupt or missing file
94
+ }
95
+ }
96
+ }
97
+ return cache;
98
+ }
99
+ /**
100
+ * Persist cache to disk for the given sessionId and/or profileId.
101
+ *
102
+ * Storage locations:
103
+ * - Session cache: always in temp directory (ephemeral)
104
+ * - Profile cache: in profileCacheDir/.once-cache/profile.json if provided, else temp directory
105
+ */
106
+ persist(sessionId, profileId, profileCacheDir) {
107
+ const defaultDir = getOnceCacheDir();
108
+ // Session cache always in temp directory
109
+ if (sessionId) {
110
+ try {
111
+ mkdirSync(defaultDir, { recursive: true });
112
+ const file = join(defaultDir, `once-session-${sanitizeCacheId(sessionId)}.json`);
113
+ const cacheObj = {};
114
+ for (const [stepId, outputs] of this.sessionCache) {
115
+ cacheObj[stepId] = outputs;
116
+ }
117
+ writeFileSync(file, JSON.stringify(cacheObj, null, 2), 'utf-8');
118
+ }
119
+ catch {
120
+ // Ignore write errors
121
+ }
122
+ }
123
+ // Profile cache in pack directory if provided, else default temp dir
124
+ if (profileId) {
125
+ let profileFile;
126
+ let profileDir;
127
+ if (profileCacheDir) {
128
+ profileDir = join(profileCacheDir, '.once-cache');
129
+ profileFile = join(profileDir, 'profile.json');
130
+ }
131
+ else {
132
+ profileDir = defaultDir;
133
+ profileFile = join(defaultDir, `once-profile-${sanitizeCacheId(profileId)}.json`);
134
+ }
135
+ try {
136
+ mkdirSync(profileDir, { recursive: true });
137
+ const cacheObj = {};
138
+ for (const [stepId, outputs] of this.profileCache) {
139
+ cacheObj[stepId] = outputs;
140
+ }
141
+ writeFileSync(profileFile, JSON.stringify(cacheObj, null, 2), 'utf-8');
142
+ }
143
+ catch {
144
+ // Ignore write errors
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Check if a step should be skipped (already executed)
150
+ */
151
+ isExecuted(stepId, scope) {
152
+ if (!scope)
153
+ return false;
154
+ if (scope === 'session') {
155
+ return this.sessionCache.has(stepId);
156
+ }
157
+ else {
158
+ return this.profileCache.has(stepId);
159
+ }
160
+ }
161
+ /**
162
+ * Mark a step as executed with its outputs
163
+ */
164
+ markExecuted(stepId, scope, outputs = { vars: {}, collectibles: {} }) {
165
+ if (scope === 'session') {
166
+ this.sessionCache.set(stepId, outputs);
167
+ }
168
+ else {
169
+ this.profileCache.set(stepId, outputs);
170
+ }
171
+ }
172
+ /**
173
+ * Get cached outputs for a step
174
+ */
175
+ getOutputs(stepId, scope) {
176
+ const cache = scope === 'session' ? this.sessionCache : this.profileCache;
177
+ return cache.get(stepId);
178
+ }
179
+ /**
180
+ * Clear cache for a specific scope
181
+ */
182
+ clear(scope) {
183
+ if (scope === 'session') {
184
+ this.sessionCache.clear();
185
+ }
186
+ else {
187
+ this.profileCache.clear();
188
+ }
189
+ }
190
+ /**
191
+ * Clear all caches
192
+ */
193
+ clearAll() {
194
+ this.sessionCache.clear();
195
+ this.profileCache.clear();
196
+ }
197
+ }
198
+ /**
199
+ * Auth failure monitor that watches network responses
200
+ */
201
+ export class AuthFailureMonitor {
202
+ authPolicy;
203
+ failures = [];
204
+ urlRegex;
205
+ constructor(authPolicy) {
206
+ this.authPolicy = {
207
+ enabled: authPolicy.enabled ?? true,
208
+ statusCodes: authPolicy.statusCodes ?? [401, 403],
209
+ urlIncludes: authPolicy.urlIncludes ?? [],
210
+ urlRegex: authPolicy.urlRegex,
211
+ loginUrlIncludes: authPolicy.loginUrlIncludes ?? [],
212
+ maxRecoveriesPerRun: authPolicy.maxRecoveriesPerRun ?? 1,
213
+ maxStepRetryAfterRecovery: authPolicy.maxStepRetryAfterRecovery ?? 1,
214
+ cooldownMs: authPolicy.cooldownMs ?? 0,
215
+ };
216
+ if (this.authPolicy.urlRegex) {
217
+ this.urlRegex = new RegExp(this.authPolicy.urlRegex);
218
+ }
219
+ }
220
+ /**
221
+ * Check if auth policy is enabled
222
+ */
223
+ isEnabled() {
224
+ return this.authPolicy.enabled;
225
+ }
226
+ /**
227
+ * Check if a response indicates auth failure
228
+ */
229
+ isAuthFailure(url, status) {
230
+ if (!this.authPolicy.enabled)
231
+ return false;
232
+ if (!this.authPolicy.statusCodes.includes(status))
233
+ return false;
234
+ // Check URL patterns if configured
235
+ if (this.authPolicy.urlIncludes.length > 0) {
236
+ const matches = this.authPolicy.urlIncludes.some((pattern) => url.includes(pattern));
237
+ if (!matches)
238
+ return false;
239
+ }
240
+ // Check URL regex if configured
241
+ if (this.urlRegex) {
242
+ if (!this.urlRegex.test(url))
243
+ return false;
244
+ }
245
+ return true;
246
+ }
247
+ /**
248
+ * Record an auth failure
249
+ */
250
+ recordFailure(failure) {
251
+ this.failures.push(failure);
252
+ }
253
+ /**
254
+ * Get the latest auth failure
255
+ */
256
+ getLatestFailure() {
257
+ return this.failures[this.failures.length - 1];
258
+ }
259
+ /**
260
+ * Get max recoveries allowed
261
+ */
262
+ getMaxRecoveries() {
263
+ return this.authPolicy.maxRecoveriesPerRun;
264
+ }
265
+ /**
266
+ * Get max step retries after recovery
267
+ */
268
+ getMaxStepRetries() {
269
+ return this.authPolicy.maxStepRetryAfterRecovery;
270
+ }
271
+ /**
272
+ * Get cooldown delay in ms
273
+ */
274
+ getCooldownMs() {
275
+ return this.authPolicy.cooldownMs;
276
+ }
277
+ /**
278
+ * Clear recorded failures (for testing or reset)
279
+ */
280
+ clearFailures() {
281
+ this.failures = [];
282
+ }
283
+ /**
284
+ * Clear failures for a specific step ID
285
+ */
286
+ clearFailuresForStep(stepId) {
287
+ this.failures = this.failures.filter((f) => f.stepId !== stepId);
288
+ }
289
+ /**
290
+ * Get failures for a specific step ID
291
+ */
292
+ getFailuresForStep(stepId) {
293
+ return this.failures.filter((f) => f.stepId === stepId);
294
+ }
295
+ }
296
+ /**
297
+ * Auth guard for proactive checks (OFF by default)
298
+ */
299
+ export class AuthGuardChecker {
300
+ guard;
301
+ constructor(guard) {
302
+ this.guard = {
303
+ enabled: guard.enabled ?? false,
304
+ strategy: guard.strategy,
305
+ };
306
+ }
307
+ /**
308
+ * Check if guard is enabled
309
+ */
310
+ isEnabled() {
311
+ return this.guard.enabled ?? false;
312
+ }
313
+ /**
314
+ * Check auth status using configured strategy
315
+ */
316
+ async checkAuth(page) {
317
+ if (!this.isEnabled())
318
+ return true; // Guard disabled = assume auth is valid
319
+ const strategy = this.guard.strategy;
320
+ if (!strategy)
321
+ return true; // No strategy = assume auth is valid
322
+ // Check visible selector
323
+ if (strategy.visibleSelector) {
324
+ try {
325
+ const element = page.locator(strategy.visibleSelector);
326
+ const isVisible = await element.isVisible({ timeout: 5000 }).catch(() => false);
327
+ return isVisible;
328
+ }
329
+ catch {
330
+ return false;
331
+ }
332
+ }
333
+ // Check URL pattern
334
+ if (strategy.urlIncludes) {
335
+ const currentUrl = page.url();
336
+ return currentUrl.includes(strategy.urlIncludes);
337
+ }
338
+ return true; // Default: assume valid
339
+ }
340
+ }
341
+ /**
342
+ * Setup network response monitoring for browser sessions
343
+ */
344
+ export function setupBrowserAuthMonitoring(page, monitor, logger, currentStepId) {
345
+ if (!monitor.isEnabled())
346
+ return;
347
+ page.on('response', (response) => {
348
+ const url = response.url();
349
+ const status = response.status();
350
+ if (monitor.isAuthFailure(url, status)) {
351
+ monitor.recordFailure({ url, status, stepId: currentStepId });
352
+ logger.log({
353
+ type: 'auth_failure_detected',
354
+ data: {
355
+ url,
356
+ status,
357
+ stepId: currentStepId,
358
+ },
359
+ });
360
+ }
361
+ });
362
+ }
363
+ /**
364
+ * Check if a step should be skipped due to "once" cache
365
+ */
366
+ export function shouldSkipStep(step, onceCache, sessionId, profileId) {
367
+ if (!step.once)
368
+ return false;
369
+ // Determine scope: if sessionId provided, use session; otherwise use profile
370
+ const scope = step.once === 'session' && sessionId ? 'session' : 'profile';
371
+ return onceCache.isExecuted(step.id, scope);
372
+ }
373
+ /**
374
+ * Get steps that should be rerun during recovery (all steps with once flag)
375
+ */
376
+ export function getOnceSteps(steps) {
377
+ return steps.filter((step) => step.once !== undefined);
378
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Unified browser launcher
3
+ *
4
+ * Provides a unified interface for launching browsers with different engines
5
+ * (Chromium, Camoufox) and persistence modes.
6
+ */
7
+ import { type Browser, type BrowserContext, type Page } from 'playwright';
8
+ import type { BrowserEngine, BrowserSettings, BrowserPersistence } from './types.js';
9
+ /**
10
+ * Browser session returned by launchBrowser
11
+ */
12
+ export interface BrowserSession {
13
+ /**
14
+ * Browser context (for both Chromium and Camoufox)
15
+ */
16
+ context: BrowserContext;
17
+ /**
18
+ * Main page in the context
19
+ */
20
+ page: Page;
21
+ /**
22
+ * Browser instance (null for Camoufox which returns context directly)
23
+ */
24
+ browser: Browser | null;
25
+ /**
26
+ * Engine used for this session
27
+ */
28
+ engine: BrowserEngine;
29
+ /**
30
+ * Persistence mode
31
+ */
32
+ persistence: BrowserPersistence;
33
+ /**
34
+ * User data directory path (if persistence is enabled)
35
+ */
36
+ userDataDir?: string;
37
+ /**
38
+ * Close the browser session
39
+ */
40
+ close(): Promise<void>;
41
+ }
42
+ /**
43
+ * Configuration for launching a browser
44
+ */
45
+ export interface LaunchBrowserConfig {
46
+ /**
47
+ * Browser settings from task pack
48
+ */
49
+ browserSettings?: BrowserSettings;
50
+ /**
51
+ * Run in headless mode (default: true)
52
+ */
53
+ headless?: boolean;
54
+ /**
55
+ * Session ID for session persistence
56
+ */
57
+ sessionId?: string;
58
+ /**
59
+ * Pack directory path for profile persistence
60
+ */
61
+ packPath?: string;
62
+ }
63
+ /**
64
+ * Launches a browser with the specified configuration
65
+ *
66
+ * @param config - Browser launch configuration
67
+ * @returns Browser session with context, page, and close function
68
+ */
69
+ export declare function launchBrowser(config: LaunchBrowserConfig): Promise<BrowserSession>;
70
+ /**
71
+ * Checks if a browser engine is available
72
+ */
73
+ export declare function isBrowserEngineAvailable(engine: BrowserEngine): Promise<boolean>;
74
+ //# sourceMappingURL=browserLauncher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserLauncher.d.ts","sourceRoot":"","sources":["../src/browserLauncher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;IACX;;OAEG;IACH,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;IACtB;;OAEG;IACH,WAAW,EAAE,kBAAkB,CAAC;IAChC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgCxF;AA4HD;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBtF"}