@vulcn/driver-browser 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,756 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BROWSER_STEP_TYPES: () => BROWSER_STEP_TYPES,
24
+ BrowserRecorder: () => BrowserRecorder,
25
+ BrowserRunner: () => BrowserRunner,
26
+ BrowserStepSchema: () => BrowserStepSchema,
27
+ checkBrowsers: () => checkBrowsers,
28
+ configSchema: () => configSchema,
29
+ default: () => index_default,
30
+ installBrowsers: () => installBrowsers,
31
+ launchBrowser: () => launchBrowser
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+ var import_zod = require("zod");
35
+
36
+ // src/browser.ts
37
+ var import_playwright = require("playwright");
38
+ var import_node_child_process = require("child_process");
39
+ var import_node_util = require("util");
40
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
41
+ var BrowserNotFoundError = class extends Error {
42
+ constructor(message) {
43
+ super(message);
44
+ this.name = "BrowserNotFoundError";
45
+ }
46
+ };
47
+ async function launchBrowser(options = {}) {
48
+ const browserType = options.browser ?? "chromium";
49
+ const headless = options.headless ?? false;
50
+ if (browserType === "chromium") {
51
+ try {
52
+ const browser = await import_playwright.chromium.launch({
53
+ channel: "chrome",
54
+ headless
55
+ });
56
+ return { browser, channel: "chrome" };
57
+ } catch {
58
+ }
59
+ try {
60
+ const browser = await import_playwright.chromium.launch({
61
+ channel: "msedge",
62
+ headless
63
+ });
64
+ return { browser, channel: "msedge" };
65
+ } catch {
66
+ }
67
+ try {
68
+ const browser = await import_playwright.chromium.launch({ headless });
69
+ return { browser, channel: "chromium" };
70
+ } catch {
71
+ throw new BrowserNotFoundError(
72
+ "No Chromium browser found. Install Chrome or run: vulcn install chromium"
73
+ );
74
+ }
75
+ }
76
+ if (browserType === "firefox") {
77
+ try {
78
+ const browser = await import_playwright.firefox.launch({ headless });
79
+ return { browser, channel: "firefox" };
80
+ } catch {
81
+ throw new BrowserNotFoundError(
82
+ "Firefox not found. Run: vulcn install firefox"
83
+ );
84
+ }
85
+ }
86
+ if (browserType === "webkit") {
87
+ try {
88
+ const browser = await import_playwright.webkit.launch({ headless });
89
+ return { browser, channel: "webkit" };
90
+ } catch {
91
+ throw new BrowserNotFoundError(
92
+ "WebKit not found. Run: vulcn install webkit"
93
+ );
94
+ }
95
+ }
96
+ throw new BrowserNotFoundError(`Unknown browser type: ${browserType}`);
97
+ }
98
+ async function installBrowsers(browsers = ["chromium"]) {
99
+ const browserArg = browsers.join(" ");
100
+ await execAsync(`npx playwright install ${browserArg}`);
101
+ }
102
+ async function checkBrowsers() {
103
+ const results = {
104
+ systemChrome: false,
105
+ systemEdge: false,
106
+ playwrightChromium: false,
107
+ playwrightFirefox: false,
108
+ playwrightWebkit: false
109
+ };
110
+ try {
111
+ const browser = await import_playwright.chromium.launch({
112
+ channel: "chrome",
113
+ headless: true
114
+ });
115
+ await browser.close();
116
+ results.systemChrome = true;
117
+ } catch {
118
+ }
119
+ try {
120
+ const browser = await import_playwright.chromium.launch({
121
+ channel: "msedge",
122
+ headless: true
123
+ });
124
+ await browser.close();
125
+ results.systemEdge = true;
126
+ } catch {
127
+ }
128
+ try {
129
+ const browser = await import_playwright.chromium.launch({ headless: true });
130
+ await browser.close();
131
+ results.playwrightChromium = true;
132
+ } catch {
133
+ }
134
+ try {
135
+ const browser = await import_playwright.firefox.launch({ headless: true });
136
+ await browser.close();
137
+ results.playwrightFirefox = true;
138
+ } catch {
139
+ }
140
+ try {
141
+ const browser = await import_playwright.webkit.launch({ headless: true });
142
+ await browser.close();
143
+ results.playwrightWebkit = true;
144
+ } catch {
145
+ }
146
+ return results;
147
+ }
148
+
149
+ // src/recorder.ts
150
+ var BrowserRecorder = class _BrowserRecorder {
151
+ /**
152
+ * Start a new recording session
153
+ */
154
+ static async start(config, _options = {}) {
155
+ const { startUrl, browser: browserType, viewport, headless } = config;
156
+ if (!startUrl) {
157
+ throw new Error("startUrl is required for browser recording");
158
+ }
159
+ const { browser } = await launchBrowser({
160
+ browser: browserType,
161
+ headless
162
+ });
163
+ const context = await browser.newContext({ viewport });
164
+ const page = await context.newPage();
165
+ await page.goto(startUrl);
166
+ const startTime = Date.now();
167
+ const steps = [];
168
+ let stepCounter = 0;
169
+ const generateStepId = () => {
170
+ stepCounter++;
171
+ return `step_${String(stepCounter).padStart(3, "0")}`;
172
+ };
173
+ steps.push({
174
+ id: generateStepId(),
175
+ type: "browser.navigate",
176
+ url: startUrl,
177
+ timestamp: 0
178
+ });
179
+ _BrowserRecorder.attachListeners(page, steps, startTime, generateStepId);
180
+ return {
181
+ async stop() {
182
+ const session = {
183
+ name: `Recording ${(/* @__PURE__ */ new Date()).toISOString()}`,
184
+ driver: "browser",
185
+ driverConfig: {
186
+ browser: browserType,
187
+ viewport,
188
+ startUrl
189
+ },
190
+ steps,
191
+ metadata: {
192
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
193
+ version: "1"
194
+ }
195
+ };
196
+ await browser.close();
197
+ return session;
198
+ },
199
+ async abort() {
200
+ await browser.close();
201
+ },
202
+ getSteps() {
203
+ return [...steps];
204
+ },
205
+ addStep(step) {
206
+ steps.push({
207
+ ...step,
208
+ id: generateStepId(),
209
+ timestamp: Date.now() - startTime
210
+ });
211
+ }
212
+ };
213
+ }
214
+ /**
215
+ * Attach event listeners to the page
216
+ */
217
+ static attachListeners(page, steps, startTime, generateStepId) {
218
+ const getTimestamp = () => Date.now() - startTime;
219
+ const addStep = (step) => {
220
+ steps.push({
221
+ ...step,
222
+ id: generateStepId(),
223
+ timestamp: getTimestamp()
224
+ });
225
+ };
226
+ page.on("framenavigated", (frame) => {
227
+ if (frame === page.mainFrame()) {
228
+ const url = frame.url();
229
+ const lastStep = steps[steps.length - 1];
230
+ if (steps.length > 0 && lastStep?.type === "browser.navigate" && lastStep.url === url) {
231
+ return;
232
+ }
233
+ addStep({
234
+ type: "browser.navigate",
235
+ url
236
+ });
237
+ }
238
+ });
239
+ page.exposeFunction(
240
+ "__vulcn_record",
241
+ async (event) => {
242
+ switch (event.type) {
243
+ case "click": {
244
+ const data = event.data;
245
+ addStep({
246
+ type: "browser.click",
247
+ selector: data.selector,
248
+ position: { x: data.x, y: data.y }
249
+ });
250
+ break;
251
+ }
252
+ case "input": {
253
+ const data = event.data;
254
+ addStep({
255
+ type: "browser.input",
256
+ selector: data.selector,
257
+ value: data.value,
258
+ injectable: data.injectable
259
+ });
260
+ break;
261
+ }
262
+ case "keypress": {
263
+ const data = event.data;
264
+ addStep({
265
+ type: "browser.keypress",
266
+ key: data.key,
267
+ modifiers: data.modifiers
268
+ });
269
+ break;
270
+ }
271
+ }
272
+ }
273
+ );
274
+ page.on("load", async () => {
275
+ await _BrowserRecorder.injectRecordingScript(page);
276
+ });
277
+ _BrowserRecorder.injectRecordingScript(page);
278
+ }
279
+ /**
280
+ * Inject the recording script into the page
281
+ */
282
+ static async injectRecordingScript(page) {
283
+ await page.evaluate(`
284
+ (function() {
285
+ if (window.__vulcn_injected) return;
286
+ window.__vulcn_injected = true;
287
+
288
+ var textInputTypes = ['text', 'password', 'email', 'search', 'url', 'tel', 'number'];
289
+
290
+ function getSelector(el) {
291
+ if (el.id) {
292
+ return '#' + CSS.escape(el.id);
293
+ }
294
+ if (el.name) {
295
+ var tag = el.tagName.toLowerCase();
296
+ var nameSelector = tag + '[name="' + el.name + '"]';
297
+ if (document.querySelectorAll(nameSelector).length === 1) {
298
+ return nameSelector;
299
+ }
300
+ }
301
+ if (el.dataset && el.dataset.testid) {
302
+ return '[data-testid="' + el.dataset.testid + '"]';
303
+ }
304
+ if (el.tagName === 'INPUT' && el.type && el.name) {
305
+ var inputSelector = 'input[type="' + el.type + '"][name="' + el.name + '"]';
306
+ if (document.querySelectorAll(inputSelector).length === 1) {
307
+ return inputSelector;
308
+ }
309
+ }
310
+ if (el.className && typeof el.className === 'string') {
311
+ var classes = el.className.trim().split(/\\s+/).filter(function(c) { return c.length > 0; });
312
+ if (classes.length > 0) {
313
+ var classSelector = el.tagName.toLowerCase() + '.' + classes.map(function(c) { return CSS.escape(c); }).join('.');
314
+ if (document.querySelectorAll(classSelector).length === 1) {
315
+ return classSelector;
316
+ }
317
+ }
318
+ }
319
+ var path = [];
320
+ var current = el;
321
+ while (current && current !== document.body) {
322
+ var tag = current.tagName.toLowerCase();
323
+ var parent = current.parentElement;
324
+ if (parent) {
325
+ var siblings = Array.from(parent.children).filter(function(c) { return c.tagName === current.tagName; });
326
+ if (siblings.length > 1) {
327
+ var index = siblings.indexOf(current) + 1;
328
+ tag = tag + ':nth-of-type(' + index + ')';
329
+ }
330
+ }
331
+ path.unshift(tag);
332
+ current = parent;
333
+ }
334
+ return path.join(' > ');
335
+ }
336
+
337
+ function getInputType(el) {
338
+ if (el.tagName === 'INPUT') return el.type || 'text';
339
+ if (el.tagName === 'TEXTAREA') return 'textarea';
340
+ if (el.tagName === 'SELECT') return 'select';
341
+ return null;
342
+ }
343
+
344
+ function isTextInjectable(el) {
345
+ var inputType = getInputType(el);
346
+ if (!inputType) return false;
347
+ if (inputType === 'textarea') return true;
348
+ if (inputType === 'select') return false;
349
+ return textInputTypes.indexOf(inputType) !== -1;
350
+ }
351
+
352
+ document.addEventListener('click', function(e) {
353
+ var target = e.target;
354
+ window.__vulcn_record({
355
+ type: 'click',
356
+ data: {
357
+ selector: getSelector(target),
358
+ x: e.clientX,
359
+ y: e.clientY
360
+ }
361
+ });
362
+ }, true);
363
+
364
+ document.addEventListener('change', function(e) {
365
+ var target = e.target;
366
+ if ('value' in target) {
367
+ var inputType = getInputType(target);
368
+ window.__vulcn_record({
369
+ type: 'input',
370
+ data: {
371
+ selector: getSelector(target),
372
+ value: target.value,
373
+ inputType: inputType,
374
+ injectable: isTextInjectable(target)
375
+ }
376
+ });
377
+ }
378
+ }, true);
379
+
380
+ document.addEventListener('keydown', function(e) {
381
+ if (e.ctrlKey || e.metaKey || e.altKey) {
382
+ var modifiers = [];
383
+ if (e.ctrlKey) modifiers.push('ctrl');
384
+ if (e.metaKey) modifiers.push('meta');
385
+ if (e.altKey) modifiers.push('alt');
386
+ if (e.shiftKey) modifiers.push('shift');
387
+
388
+ window.__vulcn_record({
389
+ type: 'keypress',
390
+ data: {
391
+ key: e.key,
392
+ modifiers: modifiers
393
+ }
394
+ });
395
+ }
396
+ }, true);
397
+ })();
398
+ `);
399
+ }
400
+ };
401
+
402
+ // src/runner.ts
403
+ var BrowserRunner = class _BrowserRunner {
404
+ /**
405
+ * Execute a session with security payloads
406
+ */
407
+ static async execute(session, ctx) {
408
+ const config = session.driverConfig;
409
+ const browserType = config.browser ?? "chromium";
410
+ const viewport = config.viewport ?? {
411
+ width: 1280,
412
+ height: 720
413
+ };
414
+ const startUrl = config.startUrl;
415
+ const headless = ctx.options.headless ?? true;
416
+ const startTime = Date.now();
417
+ const errors = [];
418
+ let payloadsTested = 0;
419
+ const payloads = ctx.payloads;
420
+ if (payloads.length === 0) {
421
+ return {
422
+ findings: [],
423
+ stepsExecuted: session.steps.length,
424
+ payloadsTested: 0,
425
+ duration: Date.now() - startTime,
426
+ errors: [
427
+ "No payloads loaded. Add a payload plugin or configure payloads."
428
+ ]
429
+ };
430
+ }
431
+ const { browser } = await launchBrowser({
432
+ browser: browserType,
433
+ headless
434
+ });
435
+ const context = await browser.newContext({ viewport });
436
+ const page = await context.newPage();
437
+ const eventFindings = [];
438
+ let currentPayloadInfo = null;
439
+ const dialogHandler = async (dialog) => {
440
+ if (currentPayloadInfo) {
441
+ const message = dialog.message();
442
+ if (message.includes("vulcn") || message === currentPayloadInfo.payloadValue) {
443
+ eventFindings.push({
444
+ type: "xss",
445
+ severity: "high",
446
+ title: "XSS Confirmed - Dialog Triggered",
447
+ description: `JavaScript dialog was triggered by payload injection`,
448
+ stepId: currentPayloadInfo.stepId,
449
+ payload: currentPayloadInfo.payloadValue,
450
+ url: page.url(),
451
+ evidence: `Dialog message: ${message}`,
452
+ metadata: {
453
+ dialogType: dialog.type(),
454
+ detectionMethod: "dialog"
455
+ }
456
+ });
457
+ }
458
+ }
459
+ try {
460
+ await dialog.dismiss();
461
+ } catch {
462
+ }
463
+ };
464
+ const consoleHandler = async (msg) => {
465
+ if (currentPayloadInfo && msg.type() === "log") {
466
+ const text = msg.text();
467
+ if (text.includes("vulcn") || text.includes(currentPayloadInfo.payloadValue)) {
468
+ eventFindings.push({
469
+ type: "xss",
470
+ severity: "high",
471
+ title: "XSS Confirmed - Console Output",
472
+ description: `JavaScript console.log was triggered by payload injection`,
473
+ stepId: currentPayloadInfo.stepId,
474
+ payload: currentPayloadInfo.payloadValue,
475
+ url: page.url(),
476
+ evidence: `Console output: ${text}`,
477
+ metadata: {
478
+ consoleType: msg.type(),
479
+ detectionMethod: "console"
480
+ }
481
+ });
482
+ }
483
+ }
484
+ };
485
+ page.on("dialog", dialogHandler);
486
+ page.on("console", consoleHandler);
487
+ try {
488
+ const injectableSteps = session.steps.filter(
489
+ (step) => step.type === "browser.input" && step.injectable !== false
490
+ );
491
+ const allPayloads = [];
492
+ for (const payloadSet of payloads) {
493
+ for (const value of payloadSet.payloads) {
494
+ allPayloads.push({ payloadSet, value });
495
+ }
496
+ }
497
+ for (const injectableStep of injectableSteps) {
498
+ for (const { payloadSet, value } of allPayloads) {
499
+ try {
500
+ currentPayloadInfo = {
501
+ stepId: injectableStep.id,
502
+ payloadSet,
503
+ payloadValue: value
504
+ };
505
+ await _BrowserRunner.replayWithPayload(
506
+ page,
507
+ session,
508
+ injectableStep,
509
+ value,
510
+ startUrl
511
+ );
512
+ const reflectionFinding = await _BrowserRunner.checkReflection(
513
+ page,
514
+ injectableStep,
515
+ payloadSet,
516
+ value
517
+ );
518
+ const allFindings = [...eventFindings];
519
+ if (reflectionFinding) {
520
+ allFindings.push(reflectionFinding);
521
+ }
522
+ for (const finding of allFindings) {
523
+ ctx.addFinding(finding);
524
+ }
525
+ eventFindings.length = 0;
526
+ payloadsTested++;
527
+ ctx.options.onStepComplete?.(injectableStep.id, payloadsTested);
528
+ } catch (err) {
529
+ errors.push(`${injectableStep.id}: ${String(err)}`);
530
+ }
531
+ }
532
+ }
533
+ } finally {
534
+ page.off("dialog", dialogHandler);
535
+ page.off("console", consoleHandler);
536
+ currentPayloadInfo = null;
537
+ await browser.close();
538
+ }
539
+ return {
540
+ findings: ctx.findings,
541
+ stepsExecuted: session.steps.length,
542
+ payloadsTested,
543
+ duration: Date.now() - startTime,
544
+ errors
545
+ };
546
+ }
547
+ /**
548
+ * Replay session steps with payload injected at target step
549
+ */
550
+ static async replayWithPayload(page, session, targetStep, payloadValue, startUrl) {
551
+ await page.goto(startUrl, { waitUntil: "domcontentloaded" });
552
+ for (const step of session.steps) {
553
+ const browserStep = step;
554
+ try {
555
+ switch (browserStep.type) {
556
+ case "browser.navigate":
557
+ await page.goto(browserStep.url, { waitUntil: "domcontentloaded" });
558
+ break;
559
+ case "browser.click":
560
+ await page.click(browserStep.selector, { timeout: 5e3 });
561
+ break;
562
+ case "browser.input": {
563
+ const value = step.id === targetStep.id ? payloadValue : browserStep.value;
564
+ await page.fill(browserStep.selector, value, { timeout: 5e3 });
565
+ break;
566
+ }
567
+ case "browser.keypress": {
568
+ const modifiers = browserStep.modifiers ?? [];
569
+ for (const mod of modifiers) {
570
+ await page.keyboard.down(
571
+ mod
572
+ );
573
+ }
574
+ await page.keyboard.press(browserStep.key);
575
+ for (const mod of modifiers.reverse()) {
576
+ await page.keyboard.up(
577
+ mod
578
+ );
579
+ }
580
+ break;
581
+ }
582
+ case "browser.scroll":
583
+ if (browserStep.selector) {
584
+ await page.locator(browserStep.selector).evaluate((el, pos) => {
585
+ el.scrollTo(pos.x, pos.y);
586
+ }, browserStep.position);
587
+ } else {
588
+ await page.evaluate((pos) => {
589
+ window.scrollTo(pos.x, pos.y);
590
+ }, browserStep.position);
591
+ }
592
+ break;
593
+ case "browser.wait":
594
+ await page.waitForTimeout(browserStep.duration);
595
+ break;
596
+ }
597
+ } catch {
598
+ }
599
+ if (step.id === targetStep.id) {
600
+ await page.waitForTimeout(100);
601
+ break;
602
+ }
603
+ }
604
+ }
605
+ /**
606
+ * Check for payload reflection in page content
607
+ */
608
+ static async checkReflection(page, step, payloadSet, payloadValue) {
609
+ const content = await page.content();
610
+ for (const pattern of payloadSet.detectPatterns) {
611
+ if (pattern.test(content)) {
612
+ return {
613
+ type: payloadSet.category,
614
+ severity: _BrowserRunner.getSeverity(payloadSet.category),
615
+ title: `${payloadSet.category.toUpperCase()} vulnerability detected`,
616
+ description: `Payload pattern was reflected in page content`,
617
+ stepId: step.id,
618
+ payload: payloadValue,
619
+ url: page.url(),
620
+ evidence: content.match(pattern)?.[0]?.slice(0, 200)
621
+ };
622
+ }
623
+ }
624
+ if (content.includes(payloadValue)) {
625
+ return {
626
+ type: payloadSet.category,
627
+ severity: "medium",
628
+ title: `Potential ${payloadSet.category.toUpperCase()} - payload reflection`,
629
+ description: `Payload was reflected in page without encoding`,
630
+ stepId: step.id,
631
+ payload: payloadValue,
632
+ url: page.url()
633
+ };
634
+ }
635
+ return void 0;
636
+ }
637
+ /**
638
+ * Determine severity based on vulnerability category
639
+ */
640
+ static getSeverity(category) {
641
+ switch (category) {
642
+ case "sqli":
643
+ case "command-injection":
644
+ case "xxe":
645
+ return "critical";
646
+ case "xss":
647
+ case "ssrf":
648
+ case "path-traversal":
649
+ return "high";
650
+ case "open-redirect":
651
+ return "medium";
652
+ default:
653
+ return "medium";
654
+ }
655
+ }
656
+ };
657
+
658
+ // src/index.ts
659
+ var configSchema = import_zod.z.object({
660
+ /** Starting URL for recording */
661
+ startUrl: import_zod.z.string().url().optional(),
662
+ /** Browser type */
663
+ browser: import_zod.z.enum(["chromium", "firefox", "webkit"]).default("chromium"),
664
+ /** Viewport size */
665
+ viewport: import_zod.z.object({
666
+ width: import_zod.z.number().default(1280),
667
+ height: import_zod.z.number().default(720)
668
+ }).default({ width: 1280, height: 720 }),
669
+ /** Run headless */
670
+ headless: import_zod.z.boolean().default(false)
671
+ });
672
+ var BROWSER_STEP_TYPES = [
673
+ "browser.navigate",
674
+ "browser.click",
675
+ "browser.input",
676
+ "browser.keypress",
677
+ "browser.scroll",
678
+ "browser.wait"
679
+ ];
680
+ var BrowserStepSchema = import_zod.z.discriminatedUnion("type", [
681
+ import_zod.z.object({
682
+ id: import_zod.z.string(),
683
+ type: import_zod.z.literal("browser.navigate"),
684
+ url: import_zod.z.string(),
685
+ timestamp: import_zod.z.number()
686
+ }),
687
+ import_zod.z.object({
688
+ id: import_zod.z.string(),
689
+ type: import_zod.z.literal("browser.click"),
690
+ selector: import_zod.z.string(),
691
+ position: import_zod.z.object({ x: import_zod.z.number(), y: import_zod.z.number() }).optional(),
692
+ timestamp: import_zod.z.number()
693
+ }),
694
+ import_zod.z.object({
695
+ id: import_zod.z.string(),
696
+ type: import_zod.z.literal("browser.input"),
697
+ selector: import_zod.z.string(),
698
+ value: import_zod.z.string(),
699
+ injectable: import_zod.z.boolean().default(true),
700
+ timestamp: import_zod.z.number()
701
+ }),
702
+ import_zod.z.object({
703
+ id: import_zod.z.string(),
704
+ type: import_zod.z.literal("browser.keypress"),
705
+ key: import_zod.z.string(),
706
+ modifiers: import_zod.z.array(import_zod.z.string()).optional(),
707
+ timestamp: import_zod.z.number()
708
+ }),
709
+ import_zod.z.object({
710
+ id: import_zod.z.string(),
711
+ type: import_zod.z.literal("browser.scroll"),
712
+ selector: import_zod.z.string().optional(),
713
+ position: import_zod.z.object({ x: import_zod.z.number(), y: import_zod.z.number() }),
714
+ timestamp: import_zod.z.number()
715
+ }),
716
+ import_zod.z.object({
717
+ id: import_zod.z.string(),
718
+ type: import_zod.z.literal("browser.wait"),
719
+ duration: import_zod.z.number(),
720
+ timestamp: import_zod.z.number()
721
+ })
722
+ ]);
723
+ var recorderDriver = {
724
+ async start(config, options) {
725
+ const parsedConfig = configSchema.parse(config);
726
+ return BrowserRecorder.start(parsedConfig, options);
727
+ }
728
+ };
729
+ var runnerDriver = {
730
+ async execute(session, ctx) {
731
+ return BrowserRunner.execute(session, ctx);
732
+ }
733
+ };
734
+ var browserDriver = {
735
+ name: "browser",
736
+ version: "0.1.0",
737
+ apiVersion: 1,
738
+ description: "Browser recording driver using Playwright",
739
+ configSchema,
740
+ stepTypes: [...BROWSER_STEP_TYPES],
741
+ recorder: recorderDriver,
742
+ runner: runnerDriver
743
+ };
744
+ var index_default = browserDriver;
745
+ // Annotate the CommonJS export names for ESM import in node:
746
+ 0 && (module.exports = {
747
+ BROWSER_STEP_TYPES,
748
+ BrowserRecorder,
749
+ BrowserRunner,
750
+ BrowserStepSchema,
751
+ checkBrowsers,
752
+ configSchema,
753
+ installBrowsers,
754
+ launchBrowser
755
+ });
756
+ //# sourceMappingURL=index.cjs.map