@vulcn/driver-browser 0.1.1 → 0.1.2

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 CHANGED
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  BrowserStepSchema: () => BrowserStepSchema,
27
27
  checkBrowsers: () => checkBrowsers,
28
28
  configSchema: () => configSchema,
29
+ crawlAndBuildSessions: () => crawlAndBuildSessions,
29
30
  default: () => index_default,
30
31
  installBrowsers: () => installBrowsers,
31
32
  launchBrowser: () => launchBrowser
@@ -677,6 +678,318 @@ var BrowserRunner = class _BrowserRunner {
677
678
  }
678
679
  };
679
680
 
681
+ // src/crawler.ts
682
+ var INJECTABLE_INPUT_TYPES = /* @__PURE__ */ new Set([
683
+ "text",
684
+ "search",
685
+ "url",
686
+ "email",
687
+ "tel",
688
+ "password",
689
+ "textarea",
690
+ ""
691
+ ]);
692
+ var CRAWL_DEFAULTS = {
693
+ maxDepth: 2,
694
+ maxPages: 20,
695
+ pageTimeout: 1e4,
696
+ sameOrigin: true
697
+ };
698
+ async function crawlAndBuildSessions(config, options = {}) {
699
+ const opts = { ...CRAWL_DEFAULTS, ...options };
700
+ const startUrl = config.startUrl;
701
+ let normalizedUrl;
702
+ try {
703
+ normalizedUrl = new URL(startUrl);
704
+ } catch {
705
+ throw new Error(`Invalid URL: ${startUrl}`);
706
+ }
707
+ const origin = normalizedUrl.origin;
708
+ const visited = /* @__PURE__ */ new Set();
709
+ const allForms = [];
710
+ const queue = [[normalizedUrl.href, 0]];
711
+ const { browser } = await launchBrowser({
712
+ browser: config.browser ?? "chromium",
713
+ headless: config.headless ?? true
714
+ });
715
+ const context = await browser.newContext({
716
+ viewport: config.viewport ?? { width: 1280, height: 720 }
717
+ });
718
+ try {
719
+ while (queue.length > 0 && visited.size < opts.maxPages) {
720
+ const [url, depth] = queue.shift();
721
+ const normalizedPageUrl = normalizeUrl(url);
722
+ if (visited.has(normalizedPageUrl)) continue;
723
+ visited.add(normalizedPageUrl);
724
+ console.log(`[crawler] [depth=${depth}] Crawling: ${normalizedPageUrl}`);
725
+ const page = await context.newPage();
726
+ try {
727
+ await page.goto(normalizedPageUrl, {
728
+ waitUntil: "domcontentloaded",
729
+ timeout: opts.pageTimeout
730
+ });
731
+ await page.waitForTimeout(1e3);
732
+ const forms = await discoverForms(page, normalizedPageUrl);
733
+ allForms.push(...forms);
734
+ const injectableCount = forms.reduce(
735
+ (s, f) => s + f.inputs.filter((i) => i.injectable).length,
736
+ 0
737
+ );
738
+ console.log(
739
+ `[crawler] Found ${forms.length} form(s), ${injectableCount} injectable input(s)`
740
+ );
741
+ opts.onPageCrawled?.(normalizedPageUrl, forms.length);
742
+ if (depth < opts.maxDepth) {
743
+ const links = await discoverLinks(page, origin, opts.sameOrigin);
744
+ for (const link of links) {
745
+ const normalizedLink = normalizeUrl(link);
746
+ if (!visited.has(normalizedLink)) {
747
+ queue.push([normalizedLink, depth + 1]);
748
+ }
749
+ }
750
+ console.log(`[crawler] Found ${links.length} link(s) to follow`);
751
+ }
752
+ } catch (err) {
753
+ console.warn(
754
+ `[crawler] Failed: ${err instanceof Error ? err.message : String(err)}`
755
+ );
756
+ } finally {
757
+ await page.close();
758
+ }
759
+ }
760
+ } finally {
761
+ await browser.close();
762
+ }
763
+ console.log(
764
+ `[crawler] Complete: ${visited.size} page(s), ${allForms.length} form(s)`
765
+ );
766
+ return buildSessions(allForms);
767
+ }
768
+ async function discoverForms(page, pageUrl) {
769
+ const forms = [];
770
+ const explicitForms = await page.evaluate(() => {
771
+ const results = [];
772
+ const formElements = document.querySelectorAll("form");
773
+ formElements.forEach((form, formIndex) => {
774
+ const inputs = [];
775
+ const inputEls = form.querySelectorAll(
776
+ 'input, textarea, [contenteditable="true"]'
777
+ );
778
+ inputEls.forEach((input, inputIndex) => {
779
+ const el = input;
780
+ const type = el.tagName.toLowerCase() === "textarea" ? "textarea" : el.getAttribute("type") || "text";
781
+ const name = el.name || el.id || `input-${inputIndex}`;
782
+ let selector = "";
783
+ if (el.id) {
784
+ selector = `#${CSS.escape(el.id)}`;
785
+ } else if (el.name) {
786
+ selector = `form:nth-of-type(${formIndex + 1}) [name="${CSS.escape(el.name)}"]`;
787
+ } else {
788
+ selector = `form:nth-of-type(${formIndex + 1}) ${el.tagName.toLowerCase()}:nth-of-type(${inputIndex + 1})`;
789
+ }
790
+ inputs.push({
791
+ selector,
792
+ type,
793
+ name,
794
+ placeholder: el.placeholder || ""
795
+ });
796
+ });
797
+ let submitSelector = null;
798
+ const submitBtn = form.querySelector('button[type="submit"], input[type="submit"]') || form.querySelector("button:not([type])") || form.querySelector('button, input[type="button"]');
799
+ if (submitBtn) {
800
+ const btn = submitBtn;
801
+ if (btn.id) {
802
+ submitSelector = `#${CSS.escape(btn.id)}`;
803
+ } else {
804
+ const tag = btn.tagName.toLowerCase();
805
+ const type = btn.getAttribute("type");
806
+ if (type) {
807
+ submitSelector = `form:nth-of-type(${formIndex + 1}) ${tag}[type="${type}"]`;
808
+ } else {
809
+ submitSelector = `form:nth-of-type(${formIndex + 1}) ${tag}`;
810
+ }
811
+ }
812
+ }
813
+ results.push({
814
+ formIndex,
815
+ action: form.action || "",
816
+ method: (form.method || "GET").toUpperCase(),
817
+ inputs,
818
+ submitSelector
819
+ });
820
+ });
821
+ return results;
822
+ });
823
+ for (const form of explicitForms) {
824
+ if (form.inputs.length === 0) continue;
825
+ forms.push({
826
+ pageUrl,
827
+ formSelector: `form:nth-of-type(${form.formIndex + 1})`,
828
+ action: form.action,
829
+ method: form.method,
830
+ inputs: form.inputs.map((input) => ({
831
+ selector: input.selector,
832
+ type: input.type,
833
+ name: input.name,
834
+ injectable: INJECTABLE_INPUT_TYPES.has(input.type.toLowerCase()),
835
+ placeholder: input.placeholder || void 0
836
+ })),
837
+ submitSelector: form.submitSelector
838
+ });
839
+ }
840
+ const standaloneInputs = await page.evaluate(() => {
841
+ const results = [];
842
+ const allInputs = document.querySelectorAll(
843
+ 'input:not(form input), textarea:not(form textarea), [contenteditable="true"]:not(form [contenteditable])'
844
+ );
845
+ allInputs.forEach((input) => {
846
+ const el = input;
847
+ const type = el.tagName.toLowerCase() === "textarea" ? "textarea" : el.getAttribute("type") || "text";
848
+ const name = el.name || el.id || "";
849
+ let selector = "";
850
+ if (el.id) {
851
+ selector = `#${CSS.escape(el.id)}`;
852
+ } else if (el.name) {
853
+ selector = `[name="${CSS.escape(el.name)}"]`;
854
+ } else {
855
+ selector = `${el.tagName.toLowerCase()}[type="${type}"]`;
856
+ }
857
+ let nearbyButtonSelector = null;
858
+ const parent = el.parentElement;
859
+ if (parent) {
860
+ const btn = parent.querySelector("button") || parent.querySelector('input[type="submit"]') || parent.querySelector('input[type="button"]');
861
+ if (btn) {
862
+ const btnEl = btn;
863
+ if (btnEl.id) {
864
+ nearbyButtonSelector = `#${CSS.escape(btnEl.id)}`;
865
+ }
866
+ }
867
+ }
868
+ results.push({
869
+ selector,
870
+ type,
871
+ name,
872
+ placeholder: el.placeholder || "",
873
+ nearbyButtonSelector
874
+ });
875
+ });
876
+ return results;
877
+ });
878
+ for (const input of standaloneInputs) {
879
+ if (!INJECTABLE_INPUT_TYPES.has(input.type.toLowerCase())) continue;
880
+ forms.push({
881
+ pageUrl,
882
+ formSelector: "(standalone)",
883
+ action: pageUrl,
884
+ method: "GET",
885
+ inputs: [
886
+ {
887
+ selector: input.selector,
888
+ type: input.type,
889
+ name: input.name,
890
+ injectable: true,
891
+ placeholder: input.placeholder || void 0
892
+ }
893
+ ],
894
+ submitSelector: input.nearbyButtonSelector
895
+ });
896
+ }
897
+ return forms;
898
+ }
899
+ async function discoverLinks(page, origin, sameOrigin) {
900
+ const links = await page.evaluate(() => {
901
+ return Array.from(document.querySelectorAll("a[href]")).map((a) => a.href).filter((href) => href.startsWith("http"));
902
+ });
903
+ if (sameOrigin) {
904
+ return links.filter((link) => {
905
+ try {
906
+ return new URL(link).origin === origin;
907
+ } catch {
908
+ return false;
909
+ }
910
+ });
911
+ }
912
+ return links;
913
+ }
914
+ function buildSessions(forms) {
915
+ const targetForms = forms.filter((f) => f.inputs.some((i) => i.injectable));
916
+ return targetForms.map((form, idx) => buildSessionForForm(form, idx));
917
+ }
918
+ function buildSessionForForm(form, index) {
919
+ const steps = [];
920
+ let stepNum = 1;
921
+ steps.push({
922
+ id: `step-${stepNum++}`,
923
+ type: "browser.navigate",
924
+ url: form.pageUrl,
925
+ timestamp: Date.now()
926
+ });
927
+ const injectableInputs = form.inputs.filter((i) => i.injectable);
928
+ for (const input of injectableInputs) {
929
+ steps.push({
930
+ id: `step-${stepNum++}`,
931
+ type: "browser.input",
932
+ selector: input.selector,
933
+ value: "test",
934
+ injectable: true,
935
+ timestamp: Date.now() + stepNum * 100
936
+ });
937
+ }
938
+ if (form.submitSelector) {
939
+ steps.push({
940
+ id: `step-${stepNum++}`,
941
+ type: "browser.click",
942
+ selector: form.submitSelector,
943
+ timestamp: Date.now() + stepNum * 100
944
+ });
945
+ } else {
946
+ steps.push({
947
+ id: `step-${stepNum++}`,
948
+ type: "browser.keypress",
949
+ key: "Enter",
950
+ timestamp: Date.now() + stepNum * 100
951
+ });
952
+ }
953
+ const inputNames = injectableInputs.map((i) => i.name || i.type).join(", ");
954
+ const pagePath = (() => {
955
+ try {
956
+ return new URL(form.pageUrl).pathname;
957
+ } catch {
958
+ return form.pageUrl;
959
+ }
960
+ })();
961
+ return {
962
+ name: `Crawl: ${pagePath} \u2014 form ${index + 1} (${inputNames})`,
963
+ driver: "browser",
964
+ driverConfig: {
965
+ startUrl: form.pageUrl,
966
+ browser: "chromium",
967
+ headless: true,
968
+ viewport: { width: 1280, height: 720 }
969
+ },
970
+ steps,
971
+ metadata: {
972
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
973
+ version: "0.3.0",
974
+ source: "crawler",
975
+ formAction: form.action,
976
+ formMethod: form.method
977
+ }
978
+ };
979
+ }
980
+ function normalizeUrl(url) {
981
+ try {
982
+ const parsed = new URL(url);
983
+ parsed.hash = "";
984
+ if (parsed.pathname !== "/" && parsed.pathname.endsWith("/")) {
985
+ parsed.pathname = parsed.pathname.slice(0, -1);
986
+ }
987
+ return parsed.href;
988
+ } catch {
989
+ return url;
990
+ }
991
+ }
992
+
680
993
  // src/index.ts
681
994
  var configSchema = import_zod.z.object({
682
995
  /** Starting URL for recording */
@@ -746,6 +1059,18 @@ var recorderDriver = {
746
1059
  async start(config, options) {
747
1060
  const parsedConfig = configSchema.parse(config);
748
1061
  return BrowserRecorder.start(parsedConfig, options);
1062
+ },
1063
+ async crawl(config, options) {
1064
+ const parsedConfig = configSchema.parse(config);
1065
+ return crawlAndBuildSessions(
1066
+ {
1067
+ startUrl: parsedConfig.startUrl ?? "",
1068
+ browser: parsedConfig.browser,
1069
+ headless: parsedConfig.headless,
1070
+ viewport: parsedConfig.viewport
1071
+ },
1072
+ options
1073
+ );
749
1074
  }
750
1075
  };
751
1076
  var runnerDriver = {
@@ -772,6 +1097,7 @@ var index_default = browserDriver;
772
1097
  BrowserStepSchema,
773
1098
  checkBrowsers,
774
1099
  configSchema,
1100
+ crawlAndBuildSessions,
775
1101
  installBrowsers,
776
1102
  launchBrowser
777
1103
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/recorder.ts","../src/runner.ts"],"sourcesContent":["/**\n * @vulcn/driver-browser\n *\n * Browser recording driver for Vulcn.\n * Uses Playwright to record and replay web application interactions.\n *\n * Step types:\n * - browser.navigate - Navigate to a URL\n * - browser.click - Click an element\n * - browser.input - Type into an input field\n * - browser.keypress - Press a key\n * - browser.scroll - Scroll the page\n * - browser.wait - Wait for a duration\n */\n\nimport { z } from \"zod\";\nimport type {\n VulcnDriver,\n RecorderDriver,\n RunnerDriver,\n RecordingHandle,\n RecordOptions,\n Session,\n Step,\n RunContext,\n RunResult,\n} from \"@vulcn/engine\";\n\nimport { BrowserRecorder } from \"./recorder\";\nimport { BrowserRunner } from \"./runner\";\n\n/**\n * Browser driver configuration schema\n */\nexport const configSchema = z.object({\n /** Starting URL for recording */\n startUrl: z.string().url().optional(),\n\n /** Browser type */\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).default(\"chromium\"),\n\n /** Viewport size */\n viewport: z\n .object({\n width: z.number().default(1280),\n height: z.number().default(720),\n })\n .default({ width: 1280, height: 720 }),\n\n /** Run headless */\n headless: z.boolean().default(false),\n});\n\nexport type BrowserConfig = z.infer<typeof configSchema>;\n\n/**\n * Browser step types\n */\nexport const BROWSER_STEP_TYPES = [\n \"browser.navigate\",\n \"browser.click\",\n \"browser.input\",\n \"browser.keypress\",\n \"browser.scroll\",\n \"browser.wait\",\n] as const;\n\nexport type BrowserStepType = (typeof BROWSER_STEP_TYPES)[number];\n\n/**\n * Browser-specific step schemas\n */\nexport const BrowserStepSchema = z.discriminatedUnion(\"type\", [\n z.object({\n id: z.string(),\n type: z.literal(\"browser.navigate\"),\n url: z.string(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.click\"),\n selector: z.string(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.input\"),\n selector: z.string(),\n value: z.string(),\n injectable: z.boolean().default(true),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.keypress\"),\n key: z.string(),\n modifiers: z.array(z.string()).optional(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.scroll\"),\n selector: z.string().optional(),\n position: z.object({ x: z.number(), y: z.number() }),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.wait\"),\n duration: z.number(),\n timestamp: z.number(),\n }),\n]);\n\nexport type BrowserStep = z.infer<typeof BrowserStepSchema>;\n\n/**\n * Browser recorder implementation\n */\nconst recorderDriver: RecorderDriver = {\n async start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle> {\n const parsedConfig = configSchema.parse(config);\n return BrowserRecorder.start(parsedConfig, options);\n },\n};\n\n/**\n * Browser runner implementation\n */\nconst runnerDriver: RunnerDriver = {\n async execute(session: Session, ctx: RunContext): Promise<RunResult> {\n return BrowserRunner.execute(session, ctx);\n },\n};\n\n/**\n * Browser driver for Vulcn\n */\nconst browserDriver: VulcnDriver = {\n name: \"browser\",\n version: \"0.1.0\",\n apiVersion: 1,\n description: \"Browser recording driver using Playwright\",\n configSchema,\n stepTypes: [...BROWSER_STEP_TYPES],\n recorder: recorderDriver,\n runner: runnerDriver,\n};\n\nexport default browserDriver;\n\n// Re-export utilities\nexport { BrowserRecorder } from \"./recorder\";\nexport { BrowserRunner } from \"./runner\";\nexport { launchBrowser, checkBrowsers, installBrowsers } from \"./browser\";\n","/**\n * Browser utilities for @vulcn/driver-browser\n * Smart browser launching with system browser fallback\n */\n\nimport { chromium, firefox, webkit, type Browser } from \"playwright\";\nimport { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\nexport type BrowserType = \"chromium\" | \"firefox\" | \"webkit\";\n\nexport interface LaunchOptions {\n browser?: BrowserType;\n headless?: boolean;\n}\n\nexport interface BrowserLaunchResult {\n browser: Browser;\n channel?: string;\n}\n\nexport class BrowserNotFoundError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BrowserNotFoundError\";\n }\n}\n\n/**\n * Launch a browser with smart fallback:\n * 1. Try system Chrome/Edge first (zero-install experience)\n * 2. Fall back to Playwright's bundled browsers\n */\nexport async function launchBrowser(\n options: LaunchOptions = {},\n): Promise<BrowserLaunchResult> {\n const browserType = options.browser ?? \"chromium\";\n const headless = options.headless ?? false;\n\n // For Chromium, try system browsers first\n if (browserType === \"chromium\") {\n // Try system Chrome\n try {\n const browser = await chromium.launch({\n channel: \"chrome\",\n headless,\n });\n return { browser, channel: \"chrome\" };\n } catch {\n // Chrome not available\n }\n\n // Try system Edge\n try {\n const browser = await chromium.launch({\n channel: \"msedge\",\n headless,\n });\n return { browser, channel: \"msedge\" };\n } catch {\n // Edge not available\n }\n\n // Fall back to Playwright's bundled Chromium\n try {\n const browser = await chromium.launch({ headless });\n return { browser, channel: \"chromium\" };\n } catch {\n throw new BrowserNotFoundError(\n \"No Chromium browser found. Install Chrome or run: vulcn install chromium\",\n );\n }\n }\n\n // Firefox\n if (browserType === \"firefox\") {\n try {\n const browser = await firefox.launch({ headless });\n return { browser, channel: \"firefox\" };\n } catch {\n throw new BrowserNotFoundError(\n \"Firefox not found. Run: vulcn install firefox\",\n );\n }\n }\n\n // WebKit\n if (browserType === \"webkit\") {\n try {\n const browser = await webkit.launch({ headless });\n return { browser, channel: \"webkit\" };\n } catch {\n throw new BrowserNotFoundError(\n \"WebKit not found. Run: vulcn install webkit\",\n );\n }\n }\n\n throw new BrowserNotFoundError(`Unknown browser type: ${browserType}`);\n}\n\n/**\n * Install Playwright browsers\n */\nexport async function installBrowsers(\n browsers: BrowserType[] = [\"chromium\"],\n): Promise<void> {\n const browserArg = browsers.join(\" \");\n await execAsync(`npx playwright install ${browserArg}`);\n}\n\n/**\n * Check which browsers are available\n */\nexport async function checkBrowsers(): Promise<{\n systemChrome: boolean;\n systemEdge: boolean;\n playwrightChromium: boolean;\n playwrightFirefox: boolean;\n playwrightWebkit: boolean;\n}> {\n const results = {\n systemChrome: false,\n systemEdge: false,\n playwrightChromium: false,\n playwrightFirefox: false,\n playwrightWebkit: false,\n };\n\n // Check system Chrome\n try {\n const browser = await chromium.launch({\n channel: \"chrome\",\n headless: true,\n });\n await browser.close();\n results.systemChrome = true;\n } catch {\n // Not available\n }\n\n // Check system Edge\n try {\n const browser = await chromium.launch({\n channel: \"msedge\",\n headless: true,\n });\n await browser.close();\n results.systemEdge = true;\n } catch {\n // Not available\n }\n\n // Check Playwright Chromium\n try {\n const browser = await chromium.launch({ headless: true });\n await browser.close();\n results.playwrightChromium = true;\n } catch {\n // Not installed\n }\n\n // Check Playwright Firefox\n try {\n const browser = await firefox.launch({ headless: true });\n await browser.close();\n results.playwrightFirefox = true;\n } catch {\n // Not installed\n }\n\n // Check Playwright WebKit\n try {\n const browser = await webkit.launch({ headless: true });\n await browser.close();\n results.playwrightWebkit = true;\n } catch {\n // Not installed\n }\n\n return results;\n}\n","/**\n * Browser Recorder Implementation\n *\n * Records browser interactions as replayable sessions.\n * Uses Playwright for browser automation.\n */\n\nimport type { Page } from \"playwright\";\nimport type {\n RecordingHandle,\n RecordOptions,\n Session,\n Step,\n} from \"@vulcn/engine\";\n\nimport { launchBrowser } from \"./browser\";\nimport type { BrowserConfig, BrowserStep } from \"./index\";\n\n/**\n * Browser Recorder - captures browser interactions\n */\nexport class BrowserRecorder {\n /**\n * Start a new recording session\n */\n static async start(\n config: BrowserConfig,\n _options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const { startUrl, browser: browserType, viewport, headless } = config;\n\n if (!startUrl) {\n throw new Error(\"startUrl is required for browser recording\");\n }\n\n // Launch browser with smart fallback\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n\n const context = await browser.newContext({ viewport });\n const page = await context.newPage();\n\n // Navigate to start URL\n await page.goto(startUrl);\n\n // Track recording\n const startTime = Date.now();\n const steps: Step[] = [];\n let stepCounter = 0;\n\n const generateStepId = () => {\n stepCounter++;\n return `step_${String(stepCounter).padStart(3, \"0\")}`;\n };\n\n // Add initial navigation step\n steps.push({\n id: generateStepId(),\n type: \"browser.navigate\",\n url: startUrl,\n timestamp: 0,\n });\n\n // Attach event listeners\n BrowserRecorder.attachListeners(page, steps, startTime, generateStepId);\n\n return {\n async stop(): Promise<Session> {\n const session: Session = {\n name: `Recording ${new Date().toISOString()}`,\n driver: \"browser\",\n driverConfig: {\n browser: browserType,\n viewport,\n startUrl,\n },\n steps,\n metadata: {\n recordedAt: new Date().toISOString(),\n version: \"1\",\n },\n };\n\n await browser.close();\n return session;\n },\n\n async abort(): Promise<void> {\n await browser.close();\n },\n\n getSteps(): Step[] {\n return [...steps];\n },\n\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void {\n steps.push({\n ...step,\n id: generateStepId(),\n timestamp: Date.now() - startTime,\n } as Step);\n },\n };\n }\n\n /**\n * Attach event listeners to the page\n */\n private static attachListeners(\n page: Page,\n steps: Step[],\n startTime: number,\n generateStepId: () => string,\n ) {\n const getTimestamp = () => Date.now() - startTime;\n\n const addStep = (\n step: Omit<Step, \"id\" | \"timestamp\"> & { type: string },\n ) => {\n steps.push({\n ...step,\n id: generateStepId(),\n timestamp: getTimestamp(),\n } as Step);\n };\n\n // Track navigation\n page.on(\"framenavigated\", (frame) => {\n if (frame === page.mainFrame()) {\n const url = frame.url();\n // Avoid duplicate nav steps for initial load\n const lastStep = steps[steps.length - 1];\n if (\n steps.length > 0 &&\n lastStep?.type === \"browser.navigate\" &&\n (lastStep as BrowserStep & { type: \"browser.navigate\" }).url === url\n ) {\n return;\n }\n addStep({\n type: \"browser.navigate\",\n url,\n });\n }\n });\n\n // Expose recording function to browser\n page.exposeFunction(\n \"__vulcn_record\",\n async (event: { type: string; data: Record<string, unknown> }) => {\n switch (event.type) {\n case \"click\": {\n const data = event.data as {\n selector: string;\n x: number;\n y: number;\n };\n addStep({\n type: \"browser.click\",\n selector: data.selector,\n position: { x: data.x, y: data.y },\n });\n break;\n }\n case \"input\": {\n const data = event.data as {\n selector: string;\n value: string;\n inputType: string | null;\n injectable: boolean;\n };\n addStep({\n type: \"browser.input\",\n selector: data.selector,\n value: data.value,\n injectable: data.injectable,\n });\n break;\n }\n case \"keypress\": {\n const data = event.data as { key: string; modifiers?: string[] };\n addStep({\n type: \"browser.keypress\",\n key: data.key,\n modifiers: data.modifiers,\n });\n break;\n }\n }\n },\n );\n\n // Inject recording script into every frame\n page.on(\"load\", async () => {\n await BrowserRecorder.injectRecordingScript(page);\n });\n\n // Inject into initial page\n BrowserRecorder.injectRecordingScript(page);\n }\n\n /**\n * Inject the recording script into the page\n */\n private static async injectRecordingScript(page: Page) {\n await page.evaluate(`\n (function() {\n if (window.__vulcn_injected) return;\n window.__vulcn_injected = true;\n\n var textInputTypes = ['text', 'password', 'email', 'search', 'url', 'tel', 'number'];\n\n function getSelector(el) {\n if (el.id) {\n return '#' + CSS.escape(el.id);\n }\n if (el.name) {\n var tag = el.tagName.toLowerCase();\n var nameSelector = tag + '[name=\"' + el.name + '\"]';\n if (document.querySelectorAll(nameSelector).length === 1) {\n return nameSelector;\n }\n }\n if (el.dataset && el.dataset.testid) {\n return '[data-testid=\"' + el.dataset.testid + '\"]';\n }\n if (el.tagName === 'INPUT' && el.type && el.name) {\n var inputSelector = 'input[type=\"' + el.type + '\"][name=\"' + el.name + '\"]';\n if (document.querySelectorAll(inputSelector).length === 1) {\n return inputSelector;\n }\n }\n if (el.className && typeof el.className === 'string') {\n var classes = el.className.trim().split(/\\\\s+/).filter(function(c) { return c.length > 0; });\n if (classes.length > 0) {\n var classSelector = el.tagName.toLowerCase() + '.' + classes.map(function(c) { return CSS.escape(c); }).join('.');\n if (document.querySelectorAll(classSelector).length === 1) {\n return classSelector;\n }\n }\n }\n var path = [];\n var current = el;\n while (current && current !== document.body) {\n var tag = current.tagName.toLowerCase();\n var parent = current.parentElement;\n if (parent) {\n var siblings = Array.from(parent.children).filter(function(c) { return c.tagName === current.tagName; });\n if (siblings.length > 1) {\n var index = siblings.indexOf(current) + 1;\n tag = tag + ':nth-of-type(' + index + ')';\n }\n }\n path.unshift(tag);\n current = parent;\n }\n return path.join(' > ');\n }\n\n function getInputType(el) {\n if (el.tagName === 'INPUT') return el.type || 'text';\n if (el.tagName === 'TEXTAREA') return 'textarea';\n if (el.tagName === 'SELECT') return 'select';\n return null;\n }\n\n function isTextInjectable(el) {\n var inputType = getInputType(el);\n if (!inputType) return false;\n if (inputType === 'textarea') return true;\n if (inputType === 'select') return false;\n return textInputTypes.indexOf(inputType) !== -1;\n }\n\n document.addEventListener('click', function(e) {\n var target = e.target;\n window.__vulcn_record({\n type: 'click',\n data: {\n selector: getSelector(target),\n x: e.clientX,\n y: e.clientY\n }\n });\n }, true);\n\n document.addEventListener('change', function(e) {\n var target = e.target;\n if ('value' in target) {\n var inputType = getInputType(target);\n window.__vulcn_record({\n type: 'input',\n data: {\n selector: getSelector(target),\n value: target.value,\n inputType: inputType,\n injectable: isTextInjectable(target)\n }\n });\n }\n }, true);\n\n document.addEventListener('keydown', function(e) {\n if (e.ctrlKey || e.metaKey || e.altKey) {\n var modifiers = [];\n if (e.ctrlKey) modifiers.push('ctrl');\n if (e.metaKey) modifiers.push('meta');\n if (e.altKey) modifiers.push('alt');\n if (e.shiftKey) modifiers.push('shift');\n\n window.__vulcn_record({\n type: 'keypress',\n data: {\n key: e.key,\n modifiers: modifiers\n }\n });\n }\n }, true);\n })();\n `);\n }\n}\n","/**\n * Browser Runner Implementation\n *\n * Replays browser sessions with security payloads.\n * Uses plugin hooks for detection.\n */\n\nimport type { Page, Dialog, ConsoleMessage } from \"playwright\";\nimport type {\n Session,\n Step,\n RunContext,\n RunResult,\n Finding,\n RuntimePayload,\n PayloadCategory,\n} from \"@vulcn/engine\";\n\nimport { launchBrowser, type BrowserType } from \"./browser\";\nimport type { BrowserStep } from \"./index\";\n\n/**\n * Browser Runner - replays sessions with payloads\n */\nexport class BrowserRunner {\n /**\n * Execute a session with security payloads\n */\n static async execute(session: Session, ctx: RunContext): Promise<RunResult> {\n const config = session.driverConfig;\n const browserType = (config.browser as BrowserType) ?? \"chromium\";\n const viewport = (config.viewport as { width: number; height: number }) ?? {\n width: 1280,\n height: 720,\n };\n const startUrl = config.startUrl as string;\n const headless = ctx.options.headless ?? true;\n\n const startTime = Date.now();\n const errors: string[] = [];\n let payloadsTested = 0;\n\n const payloads = ctx.payloads;\n if (payloads.length === 0) {\n return {\n findings: [],\n stepsExecuted: session.steps.length,\n payloadsTested: 0,\n duration: Date.now() - startTime,\n errors: [\n \"No payloads loaded. Add a payload plugin or configure payloads.\",\n ],\n };\n }\n\n // Launch browser\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n const context = await browser.newContext({ viewport });\n const page = await context.newPage();\n\n // Event findings from dialog/console handlers\n const eventFindings: Finding[] = [];\n let currentPayloadInfo: {\n stepId: string;\n payloadSet: RuntimePayload;\n payloadValue: string;\n } | null = null;\n\n // Dialog handler (for alert-based XSS detection)\n // ANY dialog triggered during payload testing is evidence of XSS execution\n const dialogHandler = async (dialog: Dialog) => {\n if (currentPayloadInfo) {\n const message = dialog.message();\n const dialogType = dialog.type();\n\n // Skip beforeunload dialogs (not XSS-related)\n if (dialogType !== \"beforeunload\") {\n eventFindings.push({\n type: \"xss\",\n severity: \"high\",\n title: `XSS Confirmed - ${dialogType}() triggered`,\n description: `JavaScript ${dialogType}() dialog was triggered by payload injection. Message: \"${message}\"`,\n stepId: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.payloadValue,\n url: page.url(),\n evidence: `Dialog type: ${dialogType}, Message: ${message}`,\n metadata: {\n dialogType,\n dialogMessage: message,\n detectionMethod: \"dialog\",\n },\n });\n }\n }\n // Always dismiss dialogs to prevent blocking\n try {\n await dialog.dismiss();\n } catch {\n // Dialog may have already been handled\n }\n };\n\n // Console message handler\n const consoleHandler = async (msg: ConsoleMessage) => {\n if (currentPayloadInfo && msg.type() === \"log\") {\n const text = msg.text();\n if (\n text.includes(\"vulcn\") ||\n text.includes(currentPayloadInfo.payloadValue)\n ) {\n eventFindings.push({\n type: \"xss\",\n severity: \"high\",\n title: \"XSS Confirmed - Console Output\",\n description: `JavaScript console.log was triggered by payload injection`,\n stepId: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.payloadValue,\n url: page.url(),\n evidence: `Console output: ${text}`,\n metadata: {\n consoleType: msg.type(),\n detectionMethod: \"console\",\n },\n });\n }\n }\n };\n\n page.on(\"dialog\", dialogHandler);\n page.on(\"console\", consoleHandler);\n\n try {\n // Find injectable steps (browser.input with injectable=true)\n const injectableSteps = session.steps.filter(\n (\n step,\n ): step is Step & { type: \"browser.input\"; injectable?: boolean } =>\n step.type === \"browser.input\" &&\n (step as BrowserStep & { type: \"browser.input\" }).injectable !==\n false,\n );\n\n // Build flat list of all individual payloads to test\n const allPayloads: { payloadSet: RuntimePayload; value: string }[] = [];\n for (const payloadSet of payloads) {\n for (const value of payloadSet.payloads) {\n allPayloads.push({ payloadSet, value });\n }\n }\n\n // For each injectable step, test with each payload\n for (const injectableStep of injectableSteps) {\n for (const { payloadSet, value } of allPayloads) {\n try {\n currentPayloadInfo = {\n stepId: injectableStep.id,\n payloadSet,\n payloadValue: value,\n };\n\n // Replay session with payload\n await BrowserRunner.replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n\n // Check for reflection\n const reflectionFinding = await BrowserRunner.checkReflection(\n page,\n injectableStep,\n payloadSet,\n value,\n );\n\n // Collect all findings\n const allFindings = [...eventFindings];\n if (reflectionFinding) {\n allFindings.push(reflectionFinding);\n }\n\n // Add unique findings\n for (const finding of allFindings) {\n ctx.addFinding(finding);\n }\n\n // Clear event findings for next iteration\n eventFindings.length = 0;\n payloadsTested++;\n\n // Report progress\n ctx.options.onStepComplete?.(injectableStep.id, payloadsTested);\n } catch (err) {\n errors.push(`${injectableStep.id}: ${String(err)}`);\n }\n }\n }\n } finally {\n page.off(\"dialog\", dialogHandler);\n page.off(\"console\", consoleHandler);\n currentPayloadInfo = null;\n await browser.close();\n }\n\n return {\n findings: ctx.findings,\n stepsExecuted: session.steps.length,\n payloadsTested,\n duration: Date.now() - startTime,\n errors,\n };\n }\n\n /**\n * Replay session steps with payload injected at target step\n *\n * IMPORTANT: We replay ALL steps, not just up to the injectable step.\n * The injection replaces the input value, but subsequent steps (like\n * clicking submit) must still execute so the payload reaches the server\n * and gets reflected back in the response.\n */\n private static async replayWithPayload(\n page: Page,\n session: Session,\n targetStep: Step & { type: \"browser.input\" },\n payloadValue: string,\n startUrl: string,\n ): Promise<void> {\n // Navigate to start\n await page.goto(startUrl, { waitUntil: \"domcontentloaded\" });\n\n let injected = false;\n\n // Replay ALL steps — inject payload at the target input step,\n // but continue replaying remaining steps (clicks, navigations)\n // so forms get submitted and payloads reach the server\n for (const step of session.steps) {\n const browserStep = step as BrowserStep;\n\n try {\n switch (browserStep.type) {\n case \"browser.navigate\":\n // Skip post-submission navigates that have session-specific URLs\n // (they'll happen naturally from form submission)\n if (injected && browserStep.url.includes(\"sid=\")) {\n continue;\n }\n await page.goto(browserStep.url, { waitUntil: \"domcontentloaded\" });\n break;\n\n case \"browser.click\":\n // If this click is after injection, wait for potential navigation\n if (injected) {\n await Promise.all([\n page\n .waitForNavigation({\n waitUntil: \"domcontentloaded\",\n timeout: 5000,\n })\n .catch(() => {}),\n page.click(browserStep.selector, { timeout: 5000 }),\n ]);\n } else {\n await page.click(browserStep.selector, { timeout: 5000 });\n }\n break;\n\n case \"browser.input\": {\n // Inject payload for target step\n const value =\n step.id === targetStep.id ? payloadValue : browserStep.value;\n await page.fill(browserStep.selector, value, { timeout: 5000 });\n if (step.id === targetStep.id) {\n injected = true;\n }\n break;\n }\n\n case \"browser.keypress\": {\n const modifiers = browserStep.modifiers ?? [];\n for (const mod of modifiers) {\n await page.keyboard.down(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\n }\n await page.keyboard.press(browserStep.key);\n for (const mod of modifiers.reverse()) {\n await page.keyboard.up(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\n }\n break;\n }\n\n case \"browser.scroll\":\n if (browserStep.selector) {\n await page.locator(browserStep.selector).evaluate((el, pos) => {\n el.scrollTo(pos.x, pos.y);\n }, browserStep.position);\n } else {\n await page.evaluate((pos) => {\n window.scrollTo(pos.x, pos.y);\n }, browserStep.position);\n }\n break;\n\n case \"browser.wait\":\n await page.waitForTimeout(browserStep.duration);\n break;\n }\n } catch {\n // Step failed, continue to next\n }\n }\n\n // Wait for any scripts to execute after all steps complete\n await page.waitForTimeout(500);\n }\n\n /**\n * Check for payload reflection in page content\n */\n private static async checkReflection(\n page: Page,\n step: Step & { type: \"browser.input\" },\n payloadSet: RuntimePayload,\n payloadValue: string,\n ): Promise<Finding | undefined> {\n const content = await page.content();\n\n // Check for reflection patterns\n for (const pattern of payloadSet.detectPatterns) {\n if (pattern.test(content)) {\n return {\n type: payloadSet.category,\n severity: BrowserRunner.getSeverity(payloadSet.category),\n title: `${payloadSet.category.toUpperCase()} vulnerability detected`,\n description: `Payload pattern was reflected in page content`,\n stepId: step.id,\n payload: payloadValue,\n url: page.url(),\n evidence: content.match(pattern)?.[0]?.slice(0, 200),\n };\n }\n }\n\n // Check if payload appears verbatim\n if (content.includes(payloadValue)) {\n return {\n type: payloadSet.category,\n severity: \"medium\",\n title: `Potential ${payloadSet.category.toUpperCase()} - payload reflection`,\n description: `Payload was reflected in page without encoding`,\n stepId: step.id,\n payload: payloadValue,\n url: page.url(),\n };\n }\n\n return undefined;\n }\n\n /**\n * Determine severity based on vulnerability category\n */\n private static getSeverity(\n category: PayloadCategory,\n ): \"critical\" | \"high\" | \"medium\" | \"low\" | \"info\" {\n switch (category) {\n case \"sqli\":\n case \"command-injection\":\n case \"xxe\":\n return \"critical\";\n case \"xss\":\n case \"ssrf\":\n case \"path-traversal\":\n return \"high\";\n case \"open-redirect\":\n return \"medium\";\n default:\n return \"medium\";\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/recorder.ts","../src/runner.ts","../src/crawler.ts"],"sourcesContent":["/**\n * @vulcn/driver-browser\n *\n * Browser recording driver for Vulcn.\n * Uses Playwright to record and replay web application interactions.\n *\n * Step types:\n * - browser.navigate - Navigate to a URL\n * - browser.click - Click an element\n * - browser.input - Type into an input field\n * - browser.keypress - Press a key\n * - browser.scroll - Scroll the page\n * - browser.wait - Wait for a duration\n */\n\nimport { z } from \"zod\";\nimport type {\n VulcnDriver,\n RecorderDriver,\n RunnerDriver,\n RecordingHandle,\n RecordOptions,\n CrawlOptions,\n Session,\n Step,\n RunContext,\n RunResult,\n} from \"@vulcn/engine\";\n\nimport { BrowserRecorder } from \"./recorder\";\nimport { BrowserRunner } from \"./runner\";\nimport { crawlAndBuildSessions } from \"./crawler\";\n\n/**\n * Browser driver configuration schema\n */\nexport const configSchema = z.object({\n /** Starting URL for recording */\n startUrl: z.string().url().optional(),\n\n /** Browser type */\n browser: z.enum([\"chromium\", \"firefox\", \"webkit\"]).default(\"chromium\"),\n\n /** Viewport size */\n viewport: z\n .object({\n width: z.number().default(1280),\n height: z.number().default(720),\n })\n .default({ width: 1280, height: 720 }),\n\n /** Run headless */\n headless: z.boolean().default(false),\n});\n\nexport type BrowserConfig = z.infer<typeof configSchema>;\n\n/**\n * Browser step types\n */\nexport const BROWSER_STEP_TYPES = [\n \"browser.navigate\",\n \"browser.click\",\n \"browser.input\",\n \"browser.keypress\",\n \"browser.scroll\",\n \"browser.wait\",\n] as const;\n\nexport type BrowserStepType = (typeof BROWSER_STEP_TYPES)[number];\n\n/**\n * Browser-specific step schemas\n */\nexport const BrowserStepSchema = z.discriminatedUnion(\"type\", [\n z.object({\n id: z.string(),\n type: z.literal(\"browser.navigate\"),\n url: z.string(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.click\"),\n selector: z.string(),\n position: z.object({ x: z.number(), y: z.number() }).optional(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.input\"),\n selector: z.string(),\n value: z.string(),\n injectable: z.boolean().default(true),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.keypress\"),\n key: z.string(),\n modifiers: z.array(z.string()).optional(),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.scroll\"),\n selector: z.string().optional(),\n position: z.object({ x: z.number(), y: z.number() }),\n timestamp: z.number(),\n }),\n z.object({\n id: z.string(),\n type: z.literal(\"browser.wait\"),\n duration: z.number(),\n timestamp: z.number(),\n }),\n]);\n\nexport type BrowserStep = z.infer<typeof BrowserStepSchema>;\n\n/**\n * Browser recorder implementation\n */\nconst recorderDriver: RecorderDriver = {\n async start(\n config: Record<string, unknown>,\n options: RecordOptions,\n ): Promise<RecordingHandle> {\n const parsedConfig = configSchema.parse(config);\n return BrowserRecorder.start(parsedConfig, options);\n },\n\n async crawl(\n config: Record<string, unknown>,\n options: CrawlOptions,\n ): Promise<Session[]> {\n const parsedConfig = configSchema.parse(config);\n return crawlAndBuildSessions(\n {\n startUrl: parsedConfig.startUrl ?? \"\",\n browser: parsedConfig.browser,\n headless: parsedConfig.headless,\n viewport: parsedConfig.viewport,\n },\n options,\n );\n },\n};\n\n/**\n * Browser runner implementation\n */\nconst runnerDriver: RunnerDriver = {\n async execute(session: Session, ctx: RunContext): Promise<RunResult> {\n return BrowserRunner.execute(session, ctx);\n },\n};\n\n/**\n * Browser driver for Vulcn\n */\nconst browserDriver: VulcnDriver = {\n name: \"browser\",\n version: \"0.1.0\",\n apiVersion: 1,\n description: \"Browser recording driver using Playwright\",\n configSchema,\n stepTypes: [...BROWSER_STEP_TYPES],\n recorder: recorderDriver,\n runner: runnerDriver,\n};\n\nexport default browserDriver;\n\n// Re-export utilities\nexport { BrowserRecorder } from \"./recorder\";\nexport { BrowserRunner } from \"./runner\";\nexport { crawlAndBuildSessions } from \"./crawler\";\nexport { launchBrowser, checkBrowsers, installBrowsers } from \"./browser\";\n","/**\n * Browser utilities for @vulcn/driver-browser\n * Smart browser launching with system browser fallback\n */\n\nimport { chromium, firefox, webkit, type Browser } from \"playwright\";\nimport { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\nexport type BrowserType = \"chromium\" | \"firefox\" | \"webkit\";\n\nexport interface LaunchOptions {\n browser?: BrowserType;\n headless?: boolean;\n}\n\nexport interface BrowserLaunchResult {\n browser: Browser;\n channel?: string;\n}\n\nexport class BrowserNotFoundError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BrowserNotFoundError\";\n }\n}\n\n/**\n * Launch a browser with smart fallback:\n * 1. Try system Chrome/Edge first (zero-install experience)\n * 2. Fall back to Playwright's bundled browsers\n */\nexport async function launchBrowser(\n options: LaunchOptions = {},\n): Promise<BrowserLaunchResult> {\n const browserType = options.browser ?? \"chromium\";\n const headless = options.headless ?? false;\n\n // For Chromium, try system browsers first\n if (browserType === \"chromium\") {\n // Try system Chrome\n try {\n const browser = await chromium.launch({\n channel: \"chrome\",\n headless,\n });\n return { browser, channel: \"chrome\" };\n } catch {\n // Chrome not available\n }\n\n // Try system Edge\n try {\n const browser = await chromium.launch({\n channel: \"msedge\",\n headless,\n });\n return { browser, channel: \"msedge\" };\n } catch {\n // Edge not available\n }\n\n // Fall back to Playwright's bundled Chromium\n try {\n const browser = await chromium.launch({ headless });\n return { browser, channel: \"chromium\" };\n } catch {\n throw new BrowserNotFoundError(\n \"No Chromium browser found. Install Chrome or run: vulcn install chromium\",\n );\n }\n }\n\n // Firefox\n if (browserType === \"firefox\") {\n try {\n const browser = await firefox.launch({ headless });\n return { browser, channel: \"firefox\" };\n } catch {\n throw new BrowserNotFoundError(\n \"Firefox not found. Run: vulcn install firefox\",\n );\n }\n }\n\n // WebKit\n if (browserType === \"webkit\") {\n try {\n const browser = await webkit.launch({ headless });\n return { browser, channel: \"webkit\" };\n } catch {\n throw new BrowserNotFoundError(\n \"WebKit not found. Run: vulcn install webkit\",\n );\n }\n }\n\n throw new BrowserNotFoundError(`Unknown browser type: ${browserType}`);\n}\n\n/**\n * Install Playwright browsers\n */\nexport async function installBrowsers(\n browsers: BrowserType[] = [\"chromium\"],\n): Promise<void> {\n const browserArg = browsers.join(\" \");\n await execAsync(`npx playwright install ${browserArg}`);\n}\n\n/**\n * Check which browsers are available\n */\nexport async function checkBrowsers(): Promise<{\n systemChrome: boolean;\n systemEdge: boolean;\n playwrightChromium: boolean;\n playwrightFirefox: boolean;\n playwrightWebkit: boolean;\n}> {\n const results = {\n systemChrome: false,\n systemEdge: false,\n playwrightChromium: false,\n playwrightFirefox: false,\n playwrightWebkit: false,\n };\n\n // Check system Chrome\n try {\n const browser = await chromium.launch({\n channel: \"chrome\",\n headless: true,\n });\n await browser.close();\n results.systemChrome = true;\n } catch {\n // Not available\n }\n\n // Check system Edge\n try {\n const browser = await chromium.launch({\n channel: \"msedge\",\n headless: true,\n });\n await browser.close();\n results.systemEdge = true;\n } catch {\n // Not available\n }\n\n // Check Playwright Chromium\n try {\n const browser = await chromium.launch({ headless: true });\n await browser.close();\n results.playwrightChromium = true;\n } catch {\n // Not installed\n }\n\n // Check Playwright Firefox\n try {\n const browser = await firefox.launch({ headless: true });\n await browser.close();\n results.playwrightFirefox = true;\n } catch {\n // Not installed\n }\n\n // Check Playwright WebKit\n try {\n const browser = await webkit.launch({ headless: true });\n await browser.close();\n results.playwrightWebkit = true;\n } catch {\n // Not installed\n }\n\n return results;\n}\n","/**\n * Browser Recorder Implementation\n *\n * Records browser interactions as replayable sessions.\n * Uses Playwright for browser automation.\n */\n\nimport type { Page } from \"playwright\";\nimport type {\n RecordingHandle,\n RecordOptions,\n Session,\n Step,\n} from \"@vulcn/engine\";\n\nimport { launchBrowser } from \"./browser\";\nimport type { BrowserConfig, BrowserStep } from \"./index\";\n\n/**\n * Browser Recorder - captures browser interactions\n */\nexport class BrowserRecorder {\n /**\n * Start a new recording session\n */\n static async start(\n config: BrowserConfig,\n _options: RecordOptions = {},\n ): Promise<RecordingHandle> {\n const { startUrl, browser: browserType, viewport, headless } = config;\n\n if (!startUrl) {\n throw new Error(\"startUrl is required for browser recording\");\n }\n\n // Launch browser with smart fallback\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n\n const context = await browser.newContext({ viewport });\n const page = await context.newPage();\n\n // Navigate to start URL\n await page.goto(startUrl);\n\n // Track recording\n const startTime = Date.now();\n const steps: Step[] = [];\n let stepCounter = 0;\n\n const generateStepId = () => {\n stepCounter++;\n return `step_${String(stepCounter).padStart(3, \"0\")}`;\n };\n\n // Add initial navigation step\n steps.push({\n id: generateStepId(),\n type: \"browser.navigate\",\n url: startUrl,\n timestamp: 0,\n });\n\n // Attach event listeners\n BrowserRecorder.attachListeners(page, steps, startTime, generateStepId);\n\n return {\n async stop(): Promise<Session> {\n const session: Session = {\n name: `Recording ${new Date().toISOString()}`,\n driver: \"browser\",\n driverConfig: {\n browser: browserType,\n viewport,\n startUrl,\n },\n steps,\n metadata: {\n recordedAt: new Date().toISOString(),\n version: \"1\",\n },\n };\n\n await browser.close();\n return session;\n },\n\n async abort(): Promise<void> {\n await browser.close();\n },\n\n getSteps(): Step[] {\n return [...steps];\n },\n\n addStep(step: Omit<Step, \"id\" | \"timestamp\">): void {\n steps.push({\n ...step,\n id: generateStepId(),\n timestamp: Date.now() - startTime,\n } as Step);\n },\n };\n }\n\n /**\n * Attach event listeners to the page\n */\n private static attachListeners(\n page: Page,\n steps: Step[],\n startTime: number,\n generateStepId: () => string,\n ) {\n const getTimestamp = () => Date.now() - startTime;\n\n const addStep = (\n step: Omit<Step, \"id\" | \"timestamp\"> & { type: string },\n ) => {\n steps.push({\n ...step,\n id: generateStepId(),\n timestamp: getTimestamp(),\n } as Step);\n };\n\n // Track navigation\n page.on(\"framenavigated\", (frame) => {\n if (frame === page.mainFrame()) {\n const url = frame.url();\n // Avoid duplicate nav steps for initial load\n const lastStep = steps[steps.length - 1];\n if (\n steps.length > 0 &&\n lastStep?.type === \"browser.navigate\" &&\n (lastStep as BrowserStep & { type: \"browser.navigate\" }).url === url\n ) {\n return;\n }\n addStep({\n type: \"browser.navigate\",\n url,\n });\n }\n });\n\n // Expose recording function to browser\n page.exposeFunction(\n \"__vulcn_record\",\n async (event: { type: string; data: Record<string, unknown> }) => {\n switch (event.type) {\n case \"click\": {\n const data = event.data as {\n selector: string;\n x: number;\n y: number;\n };\n addStep({\n type: \"browser.click\",\n selector: data.selector,\n position: { x: data.x, y: data.y },\n });\n break;\n }\n case \"input\": {\n const data = event.data as {\n selector: string;\n value: string;\n inputType: string | null;\n injectable: boolean;\n };\n addStep({\n type: \"browser.input\",\n selector: data.selector,\n value: data.value,\n injectable: data.injectable,\n });\n break;\n }\n case \"keypress\": {\n const data = event.data as { key: string; modifiers?: string[] };\n addStep({\n type: \"browser.keypress\",\n key: data.key,\n modifiers: data.modifiers,\n });\n break;\n }\n }\n },\n );\n\n // Inject recording script into every frame\n page.on(\"load\", async () => {\n await BrowserRecorder.injectRecordingScript(page);\n });\n\n // Inject into initial page\n BrowserRecorder.injectRecordingScript(page);\n }\n\n /**\n * Inject the recording script into the page\n */\n private static async injectRecordingScript(page: Page) {\n await page.evaluate(`\n (function() {\n if (window.__vulcn_injected) return;\n window.__vulcn_injected = true;\n\n var textInputTypes = ['text', 'password', 'email', 'search', 'url', 'tel', 'number'];\n\n function getSelector(el) {\n if (el.id) {\n return '#' + CSS.escape(el.id);\n }\n if (el.name) {\n var tag = el.tagName.toLowerCase();\n var nameSelector = tag + '[name=\"' + el.name + '\"]';\n if (document.querySelectorAll(nameSelector).length === 1) {\n return nameSelector;\n }\n }\n if (el.dataset && el.dataset.testid) {\n return '[data-testid=\"' + el.dataset.testid + '\"]';\n }\n if (el.tagName === 'INPUT' && el.type && el.name) {\n var inputSelector = 'input[type=\"' + el.type + '\"][name=\"' + el.name + '\"]';\n if (document.querySelectorAll(inputSelector).length === 1) {\n return inputSelector;\n }\n }\n if (el.className && typeof el.className === 'string') {\n var classes = el.className.trim().split(/\\\\s+/).filter(function(c) { return c.length > 0; });\n if (classes.length > 0) {\n var classSelector = el.tagName.toLowerCase() + '.' + classes.map(function(c) { return CSS.escape(c); }).join('.');\n if (document.querySelectorAll(classSelector).length === 1) {\n return classSelector;\n }\n }\n }\n var path = [];\n var current = el;\n while (current && current !== document.body) {\n var tag = current.tagName.toLowerCase();\n var parent = current.parentElement;\n if (parent) {\n var siblings = Array.from(parent.children).filter(function(c) { return c.tagName === current.tagName; });\n if (siblings.length > 1) {\n var index = siblings.indexOf(current) + 1;\n tag = tag + ':nth-of-type(' + index + ')';\n }\n }\n path.unshift(tag);\n current = parent;\n }\n return path.join(' > ');\n }\n\n function getInputType(el) {\n if (el.tagName === 'INPUT') return el.type || 'text';\n if (el.tagName === 'TEXTAREA') return 'textarea';\n if (el.tagName === 'SELECT') return 'select';\n return null;\n }\n\n function isTextInjectable(el) {\n var inputType = getInputType(el);\n if (!inputType) return false;\n if (inputType === 'textarea') return true;\n if (inputType === 'select') return false;\n return textInputTypes.indexOf(inputType) !== -1;\n }\n\n document.addEventListener('click', function(e) {\n var target = e.target;\n window.__vulcn_record({\n type: 'click',\n data: {\n selector: getSelector(target),\n x: e.clientX,\n y: e.clientY\n }\n });\n }, true);\n\n document.addEventListener('change', function(e) {\n var target = e.target;\n if ('value' in target) {\n var inputType = getInputType(target);\n window.__vulcn_record({\n type: 'input',\n data: {\n selector: getSelector(target),\n value: target.value,\n inputType: inputType,\n injectable: isTextInjectable(target)\n }\n });\n }\n }, true);\n\n document.addEventListener('keydown', function(e) {\n if (e.ctrlKey || e.metaKey || e.altKey) {\n var modifiers = [];\n if (e.ctrlKey) modifiers.push('ctrl');\n if (e.metaKey) modifiers.push('meta');\n if (e.altKey) modifiers.push('alt');\n if (e.shiftKey) modifiers.push('shift');\n\n window.__vulcn_record({\n type: 'keypress',\n data: {\n key: e.key,\n modifiers: modifiers\n }\n });\n }\n }, true);\n })();\n `);\n }\n}\n","/**\n * Browser Runner Implementation\n *\n * Replays browser sessions with security payloads.\n * Uses plugin hooks for detection.\n */\n\nimport type { Page, Dialog, ConsoleMessage } from \"playwright\";\nimport type {\n Session,\n Step,\n RunContext,\n RunResult,\n Finding,\n RuntimePayload,\n PayloadCategory,\n} from \"@vulcn/engine\";\n\nimport { launchBrowser, type BrowserType } from \"./browser\";\nimport type { BrowserStep } from \"./index\";\n\n/**\n * Browser Runner - replays sessions with payloads\n */\nexport class BrowserRunner {\n /**\n * Execute a session with security payloads\n */\n static async execute(session: Session, ctx: RunContext): Promise<RunResult> {\n const config = session.driverConfig;\n const browserType = (config.browser as BrowserType) ?? \"chromium\";\n const viewport = (config.viewport as { width: number; height: number }) ?? {\n width: 1280,\n height: 720,\n };\n const startUrl = config.startUrl as string;\n const headless = ctx.options.headless ?? true;\n\n const startTime = Date.now();\n const errors: string[] = [];\n let payloadsTested = 0;\n\n const payloads = ctx.payloads;\n if (payloads.length === 0) {\n return {\n findings: [],\n stepsExecuted: session.steps.length,\n payloadsTested: 0,\n duration: Date.now() - startTime,\n errors: [\n \"No payloads loaded. Add a payload plugin or configure payloads.\",\n ],\n };\n }\n\n // Launch browser\n const { browser } = await launchBrowser({\n browser: browserType,\n headless,\n });\n const context = await browser.newContext({ viewport });\n const page = await context.newPage();\n\n // Event findings from dialog/console handlers\n const eventFindings: Finding[] = [];\n let currentPayloadInfo: {\n stepId: string;\n payloadSet: RuntimePayload;\n payloadValue: string;\n } | null = null;\n\n // Dialog handler (for alert-based XSS detection)\n // ANY dialog triggered during payload testing is evidence of XSS execution\n const dialogHandler = async (dialog: Dialog) => {\n if (currentPayloadInfo) {\n const message = dialog.message();\n const dialogType = dialog.type();\n\n // Skip beforeunload dialogs (not XSS-related)\n if (dialogType !== \"beforeunload\") {\n eventFindings.push({\n type: \"xss\",\n severity: \"high\",\n title: `XSS Confirmed - ${dialogType}() triggered`,\n description: `JavaScript ${dialogType}() dialog was triggered by payload injection. Message: \"${message}\"`,\n stepId: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.payloadValue,\n url: page.url(),\n evidence: `Dialog type: ${dialogType}, Message: ${message}`,\n metadata: {\n dialogType,\n dialogMessage: message,\n detectionMethod: \"dialog\",\n },\n });\n }\n }\n // Always dismiss dialogs to prevent blocking\n try {\n await dialog.dismiss();\n } catch {\n // Dialog may have already been handled\n }\n };\n\n // Console message handler\n const consoleHandler = async (msg: ConsoleMessage) => {\n if (currentPayloadInfo && msg.type() === \"log\") {\n const text = msg.text();\n if (\n text.includes(\"vulcn\") ||\n text.includes(currentPayloadInfo.payloadValue)\n ) {\n eventFindings.push({\n type: \"xss\",\n severity: \"high\",\n title: \"XSS Confirmed - Console Output\",\n description: `JavaScript console.log was triggered by payload injection`,\n stepId: currentPayloadInfo.stepId,\n payload: currentPayloadInfo.payloadValue,\n url: page.url(),\n evidence: `Console output: ${text}`,\n metadata: {\n consoleType: msg.type(),\n detectionMethod: \"console\",\n },\n });\n }\n }\n };\n\n page.on(\"dialog\", dialogHandler);\n page.on(\"console\", consoleHandler);\n\n try {\n // Find injectable steps (browser.input with injectable=true)\n const injectableSteps = session.steps.filter(\n (\n step,\n ): step is Step & { type: \"browser.input\"; injectable?: boolean } =>\n step.type === \"browser.input\" &&\n (step as BrowserStep & { type: \"browser.input\" }).injectable !==\n false,\n );\n\n // Build flat list of all individual payloads to test\n const allPayloads: { payloadSet: RuntimePayload; value: string }[] = [];\n for (const payloadSet of payloads) {\n for (const value of payloadSet.payloads) {\n allPayloads.push({ payloadSet, value });\n }\n }\n\n // For each injectable step, test with each payload\n for (const injectableStep of injectableSteps) {\n for (const { payloadSet, value } of allPayloads) {\n try {\n currentPayloadInfo = {\n stepId: injectableStep.id,\n payloadSet,\n payloadValue: value,\n };\n\n // Replay session with payload\n await BrowserRunner.replayWithPayload(\n page,\n session,\n injectableStep,\n value,\n startUrl,\n );\n\n // Check for reflection\n const reflectionFinding = await BrowserRunner.checkReflection(\n page,\n injectableStep,\n payloadSet,\n value,\n );\n\n // Collect all findings\n const allFindings = [...eventFindings];\n if (reflectionFinding) {\n allFindings.push(reflectionFinding);\n }\n\n // Add unique findings\n for (const finding of allFindings) {\n ctx.addFinding(finding);\n }\n\n // Clear event findings for next iteration\n eventFindings.length = 0;\n payloadsTested++;\n\n // Report progress\n ctx.options.onStepComplete?.(injectableStep.id, payloadsTested);\n } catch (err) {\n errors.push(`${injectableStep.id}: ${String(err)}`);\n }\n }\n }\n } finally {\n page.off(\"dialog\", dialogHandler);\n page.off(\"console\", consoleHandler);\n currentPayloadInfo = null;\n await browser.close();\n }\n\n return {\n findings: ctx.findings,\n stepsExecuted: session.steps.length,\n payloadsTested,\n duration: Date.now() - startTime,\n errors,\n };\n }\n\n /**\n * Replay session steps with payload injected at target step\n *\n * IMPORTANT: We replay ALL steps, not just up to the injectable step.\n * The injection replaces the input value, but subsequent steps (like\n * clicking submit) must still execute so the payload reaches the server\n * and gets reflected back in the response.\n */\n private static async replayWithPayload(\n page: Page,\n session: Session,\n targetStep: Step & { type: \"browser.input\" },\n payloadValue: string,\n startUrl: string,\n ): Promise<void> {\n // Navigate to start\n await page.goto(startUrl, { waitUntil: \"domcontentloaded\" });\n\n let injected = false;\n\n // Replay ALL steps — inject payload at the target input step,\n // but continue replaying remaining steps (clicks, navigations)\n // so forms get submitted and payloads reach the server\n for (const step of session.steps) {\n const browserStep = step as BrowserStep;\n\n try {\n switch (browserStep.type) {\n case \"browser.navigate\":\n // Skip post-submission navigates that have session-specific URLs\n // (they'll happen naturally from form submission)\n if (injected && browserStep.url.includes(\"sid=\")) {\n continue;\n }\n await page.goto(browserStep.url, { waitUntil: \"domcontentloaded\" });\n break;\n\n case \"browser.click\":\n // If this click is after injection, wait for potential navigation\n if (injected) {\n await Promise.all([\n page\n .waitForNavigation({\n waitUntil: \"domcontentloaded\",\n timeout: 5000,\n })\n .catch(() => {}),\n page.click(browserStep.selector, { timeout: 5000 }),\n ]);\n } else {\n await page.click(browserStep.selector, { timeout: 5000 });\n }\n break;\n\n case \"browser.input\": {\n // Inject payload for target step\n const value =\n step.id === targetStep.id ? payloadValue : browserStep.value;\n await page.fill(browserStep.selector, value, { timeout: 5000 });\n if (step.id === targetStep.id) {\n injected = true;\n }\n break;\n }\n\n case \"browser.keypress\": {\n const modifiers = browserStep.modifiers ?? [];\n for (const mod of modifiers) {\n await page.keyboard.down(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\n }\n await page.keyboard.press(browserStep.key);\n for (const mod of modifiers.reverse()) {\n await page.keyboard.up(\n mod as \"Control\" | \"Shift\" | \"Alt\" | \"Meta\",\n );\n }\n break;\n }\n\n case \"browser.scroll\":\n if (browserStep.selector) {\n await page.locator(browserStep.selector).evaluate((el, pos) => {\n el.scrollTo(pos.x, pos.y);\n }, browserStep.position);\n } else {\n await page.evaluate((pos) => {\n window.scrollTo(pos.x, pos.y);\n }, browserStep.position);\n }\n break;\n\n case \"browser.wait\":\n await page.waitForTimeout(browserStep.duration);\n break;\n }\n } catch {\n // Step failed, continue to next\n }\n }\n\n // Wait for any scripts to execute after all steps complete\n await page.waitForTimeout(500);\n }\n\n /**\n * Check for payload reflection in page content\n */\n private static async checkReflection(\n page: Page,\n step: Step & { type: \"browser.input\" },\n payloadSet: RuntimePayload,\n payloadValue: string,\n ): Promise<Finding | undefined> {\n const content = await page.content();\n\n // Check for reflection patterns\n for (const pattern of payloadSet.detectPatterns) {\n if (pattern.test(content)) {\n return {\n type: payloadSet.category,\n severity: BrowserRunner.getSeverity(payloadSet.category),\n title: `${payloadSet.category.toUpperCase()} vulnerability detected`,\n description: `Payload pattern was reflected in page content`,\n stepId: step.id,\n payload: payloadValue,\n url: page.url(),\n evidence: content.match(pattern)?.[0]?.slice(0, 200),\n };\n }\n }\n\n // Check if payload appears verbatim\n if (content.includes(payloadValue)) {\n return {\n type: payloadSet.category,\n severity: \"medium\",\n title: `Potential ${payloadSet.category.toUpperCase()} - payload reflection`,\n description: `Payload was reflected in page without encoding`,\n stepId: step.id,\n payload: payloadValue,\n url: page.url(),\n };\n }\n\n return undefined;\n }\n\n /**\n * Determine severity based on vulnerability category\n */\n private static getSeverity(\n category: PayloadCategory,\n ): \"critical\" | \"high\" | \"medium\" | \"low\" | \"info\" {\n switch (category) {\n case \"sqli\":\n case \"command-injection\":\n case \"xxe\":\n return \"critical\";\n case \"xss\":\n case \"ssrf\":\n case \"path-traversal\":\n return \"high\";\n case \"open-redirect\":\n return \"medium\";\n default:\n return \"medium\";\n }\n }\n}\n","/**\n * Browser Crawler\n *\n * Automated session generator for the browser driver.\n * Crawls a web application using Playwright to discover:\n * - Forms with input fields and submit buttons\n * - Links to follow for deeper crawling\n *\n * Outputs Session[] that are directly compatible with BrowserRunner.\n *\n * This is the \"auto-record\" mode — instead of a human clicking around,\n * the crawler automatically discovers injection points.\n */\n\nimport type { Page, Browser, BrowserContext } from \"playwright\";\nimport type { Session, Step, CrawlOptions } from \"@vulcn/engine\";\nimport { launchBrowser, type BrowserType } from \"./browser\";\n\n// ── Internal Types ─────────────────────────────────────────────────────\n\ninterface DiscoveredInput {\n selector: string;\n type: string;\n name: string;\n injectable: boolean;\n placeholder?: string;\n}\n\ninterface DiscoveredForm {\n pageUrl: string;\n formSelector: string;\n action: string;\n method: string;\n inputs: DiscoveredInput[];\n submitSelector: string | null;\n}\n\n// Input types that accept text and can be injected with payloads\nconst INJECTABLE_INPUT_TYPES = new Set([\n \"text\",\n \"search\",\n \"url\",\n \"email\",\n \"tel\",\n \"password\",\n \"textarea\",\n \"\",\n]);\n\nconst CRAWL_DEFAULTS = {\n maxDepth: 2,\n maxPages: 20,\n pageTimeout: 10000,\n sameOrigin: true,\n};\n\n// ── Public API ─────────────────────────────────────────────────────────\n\nexport interface BrowserCrawlConfig {\n /** Starting URL */\n startUrl: string;\n /** Browser type */\n browser?: BrowserType;\n /** Run headless */\n headless?: boolean;\n /** Viewport */\n viewport?: { width: number; height: number };\n}\n\n/**\n * Crawl a URL and generate sessions.\n *\n * This is called by the browser driver's recorder.crawl() method.\n */\nexport async function crawlAndBuildSessions(\n config: BrowserCrawlConfig,\n options: CrawlOptions = {},\n): Promise<Session[]> {\n const opts = { ...CRAWL_DEFAULTS, ...options };\n const startUrl = config.startUrl;\n\n let normalizedUrl: URL;\n try {\n normalizedUrl = new URL(startUrl);\n } catch {\n throw new Error(`Invalid URL: ${startUrl}`);\n }\n\n const origin = normalizedUrl.origin;\n const visited = new Set<string>();\n const allForms: DiscoveredForm[] = [];\n\n // BFS queue: [url, depth]\n const queue: [string, number][] = [[normalizedUrl.href, 0]];\n\n // Use the shared browser launcher\n const { browser } = await launchBrowser({\n browser: config.browser ?? \"chromium\",\n headless: config.headless ?? true,\n });\n\n const context: BrowserContext = await browser.newContext({\n viewport: config.viewport ?? { width: 1280, height: 720 },\n });\n\n try {\n while (queue.length > 0 && visited.size < opts.maxPages) {\n const [url, depth] = queue.shift()!;\n const normalizedPageUrl = normalizeUrl(url);\n\n if (visited.has(normalizedPageUrl)) continue;\n visited.add(normalizedPageUrl);\n\n console.log(`[crawler] [depth=${depth}] Crawling: ${normalizedPageUrl}`);\n\n const page: Page = await context.newPage();\n\n try {\n await page.goto(normalizedPageUrl, {\n waitUntil: \"domcontentloaded\",\n timeout: opts.pageTimeout,\n });\n\n // Wait for JS-rendered content\n await page.waitForTimeout(1000);\n\n // Discover forms\n const forms = await discoverForms(page, normalizedPageUrl);\n allForms.push(...forms);\n\n const injectableCount = forms.reduce(\n (s, f) => s + f.inputs.filter((i) => i.injectable).length,\n 0,\n );\n console.log(\n `[crawler] Found ${forms.length} form(s), ${injectableCount} injectable input(s)`,\n );\n\n opts.onPageCrawled?.(normalizedPageUrl, forms.length);\n\n // Follow links\n if (depth < opts.maxDepth) {\n const links = await discoverLinks(page, origin, opts.sameOrigin);\n for (const link of links) {\n const normalizedLink = normalizeUrl(link);\n if (!visited.has(normalizedLink)) {\n queue.push([normalizedLink, depth + 1]);\n }\n }\n console.log(`[crawler] Found ${links.length} link(s) to follow`);\n }\n } catch (err) {\n console.warn(\n `[crawler] Failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n await page.close();\n }\n }\n } finally {\n await browser.close();\n }\n\n console.log(\n `[crawler] Complete: ${visited.size} page(s), ${allForms.length} form(s)`,\n );\n\n // Convert discovered forms to sessions\n return buildSessions(allForms);\n}\n\n// ── Form Discovery ─────────────────────────────────────────────────────\n\nasync function discoverForms(\n page: Page,\n pageUrl: string,\n): Promise<DiscoveredForm[]> {\n const forms: DiscoveredForm[] = [];\n\n // 1. Explicit <form> elements\n const explicitForms = await page.evaluate(() => {\n const results: Array<{\n formIndex: number;\n action: string;\n method: string;\n inputs: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n }>;\n submitSelector: string | null;\n }> = [];\n\n const formElements = document.querySelectorAll(\"form\");\n\n formElements.forEach((form, formIndex) => {\n const inputs: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n }> = [];\n\n const inputEls = form.querySelectorAll(\n 'input, textarea, [contenteditable=\"true\"]',\n );\n\n inputEls.forEach((input, inputIndex) => {\n const el = input as HTMLInputElement;\n const type =\n el.tagName.toLowerCase() === \"textarea\"\n ? \"textarea\"\n : el.getAttribute(\"type\") || \"text\";\n const name = el.name || el.id || `input-${inputIndex}`;\n\n let selector = \"\";\n if (el.id) {\n selector = `#${CSS.escape(el.id)}`;\n } else if (el.name) {\n selector = `form:nth-of-type(${formIndex + 1}) [name=\"${CSS.escape(el.name)}\"]`;\n } else {\n selector = `form:nth-of-type(${formIndex + 1}) ${el.tagName.toLowerCase()}:nth-of-type(${inputIndex + 1})`;\n }\n\n inputs.push({\n selector,\n type,\n name,\n placeholder: el.placeholder || \"\",\n });\n });\n\n // Find submit trigger\n let submitSelector: string | null = null;\n const submitBtn =\n form.querySelector('button[type=\"submit\"], input[type=\"submit\"]') ||\n form.querySelector(\"button:not([type])\") ||\n form.querySelector('button, input[type=\"button\"]');\n\n if (submitBtn) {\n const btn = submitBtn as HTMLElement;\n if (btn.id) {\n submitSelector = `#${CSS.escape(btn.id)}`;\n } else {\n const tag = btn.tagName.toLowerCase();\n const type = btn.getAttribute(\"type\");\n if (type) {\n submitSelector = `form:nth-of-type(${formIndex + 1}) ${tag}[type=\"${type}\"]`;\n } else {\n submitSelector = `form:nth-of-type(${formIndex + 1}) ${tag}`;\n }\n }\n }\n\n results.push({\n formIndex,\n action: form.action || \"\",\n method: (form.method || \"GET\").toUpperCase(),\n inputs,\n submitSelector,\n });\n });\n\n return results;\n });\n\n for (const form of explicitForms) {\n if (form.inputs.length === 0) continue;\n\n forms.push({\n pageUrl,\n formSelector: `form:nth-of-type(${form.formIndex + 1})`,\n action: form.action,\n method: form.method,\n inputs: form.inputs.map((input) => ({\n selector: input.selector,\n type: input.type,\n name: input.name,\n injectable: INJECTABLE_INPUT_TYPES.has(input.type.toLowerCase()),\n placeholder: input.placeholder || undefined,\n })),\n submitSelector: form.submitSelector,\n });\n }\n\n // 2. Standalone inputs NOT inside a <form>\n const standaloneInputs = await page.evaluate(() => {\n const results: Array<{\n selector: string;\n type: string;\n name: string;\n placeholder: string;\n nearbyButtonSelector: string | null;\n }> = [];\n\n const allInputs = document.querySelectorAll(\n 'input:not(form input), textarea:not(form textarea), [contenteditable=\"true\"]:not(form [contenteditable])',\n );\n\n allInputs.forEach((input) => {\n const el = input as HTMLInputElement;\n const type =\n el.tagName.toLowerCase() === \"textarea\"\n ? \"textarea\"\n : el.getAttribute(\"type\") || \"text\";\n const name = el.name || el.id || \"\";\n\n let selector = \"\";\n if (el.id) {\n selector = `#${CSS.escape(el.id)}`;\n } else if (el.name) {\n selector = `[name=\"${CSS.escape(el.name)}\"]`;\n } else {\n selector = `${el.tagName.toLowerCase()}[type=\"${type}\"]`;\n }\n\n // Look for nearby button\n let nearbyButtonSelector: string | null = null;\n const parent = el.parentElement;\n if (parent) {\n const btn =\n parent.querySelector(\"button\") ||\n parent.querySelector('input[type=\"submit\"]') ||\n parent.querySelector('input[type=\"button\"]');\n if (btn) {\n const btnEl = btn as HTMLElement;\n if (btnEl.id) {\n nearbyButtonSelector = `#${CSS.escape(btnEl.id)}`;\n }\n }\n }\n\n results.push({\n selector,\n type,\n name,\n placeholder: el.placeholder || \"\",\n nearbyButtonSelector,\n });\n });\n\n return results;\n });\n\n for (const input of standaloneInputs) {\n if (!INJECTABLE_INPUT_TYPES.has(input.type.toLowerCase())) continue;\n\n forms.push({\n pageUrl,\n formSelector: \"(standalone)\",\n action: pageUrl,\n method: \"GET\",\n inputs: [\n {\n selector: input.selector,\n type: input.type,\n name: input.name,\n injectable: true,\n placeholder: input.placeholder || undefined,\n },\n ],\n submitSelector: input.nearbyButtonSelector,\n });\n }\n\n return forms;\n}\n\n// ── Link Discovery ─────────────────────────────────────────────────────\n\nasync function discoverLinks(\n page: Page,\n origin: string,\n sameOrigin: boolean,\n): Promise<string[]> {\n const links = await page.evaluate(() => {\n return Array.from(document.querySelectorAll(\"a[href]\"))\n .map((a) => (a as HTMLAnchorElement).href)\n .filter((href) => href.startsWith(\"http\"));\n });\n\n if (sameOrigin) {\n return links.filter((link) => {\n try {\n return new URL(link).origin === origin;\n } catch {\n return false;\n }\n });\n }\n\n return links;\n}\n\n// ── Session Builder ────────────────────────────────────────────────────\n\n/**\n * Convert discovered forms into Vulcn sessions.\n *\n * Each form with injectable inputs becomes one session:\n * navigate → fill input(s) → click submit / press Enter\n */\nfunction buildSessions(forms: DiscoveredForm[]): Session[] {\n const targetForms = forms.filter((f) => f.inputs.some((i) => i.injectable));\n\n return targetForms.map((form, idx) => buildSessionForForm(form, idx));\n}\n\nfunction buildSessionForForm(form: DiscoveredForm, index: number): Session {\n const steps: Step[] = [];\n let stepNum = 1;\n\n // Step 1: Navigate\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.navigate\",\n url: form.pageUrl,\n timestamp: Date.now(),\n } as Step);\n\n // Steps 2+: Fill each injectable input\n const injectableInputs = form.inputs.filter((i) => i.injectable);\n\n for (const input of injectableInputs) {\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.input\",\n selector: input.selector,\n value: \"test\",\n injectable: true,\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n }\n\n // Final step: Submit\n if (form.submitSelector) {\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.click\",\n selector: form.submitSelector,\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n } else {\n // No submit button — press Enter\n steps.push({\n id: `step-${stepNum++}`,\n type: \"browser.keypress\",\n key: \"Enter\",\n timestamp: Date.now() + stepNum * 100,\n } as Step);\n }\n\n const inputNames = injectableInputs.map((i) => i.name || i.type).join(\", \");\n const pagePath = (() => {\n try {\n return new URL(form.pageUrl).pathname;\n } catch {\n return form.pageUrl;\n }\n })();\n\n return {\n name: `Crawl: ${pagePath} — form ${index + 1} (${inputNames})`,\n driver: \"browser\",\n driverConfig: {\n startUrl: form.pageUrl,\n browser: \"chromium\",\n headless: true,\n viewport: { width: 1280, height: 720 },\n },\n steps,\n metadata: {\n recordedAt: new Date().toISOString(),\n version: \"0.3.0\",\n source: \"crawler\",\n formAction: form.action,\n formMethod: form.method,\n },\n };\n}\n\n// ── Utilities ──────────────────────────────────────────────────────────\n\nfunction normalizeUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.hash = \"\";\n if (parsed.pathname !== \"/\" && parsed.pathname.endsWith(\"/\")) {\n parsed.pathname = parsed.pathname.slice(0, -1);\n }\n return parsed.href;\n } catch {\n return url;\n }\n}\n"],"mappings":"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","names":[]}