From 8ab14ebc73bd49d6b5cc08092d26e747651ca2b9 Mon Sep 17 00:00:00 2001 From: lonerapier Date: Tue, 11 Mar 2025 15:00:37 +0530 Subject: [PATCH 1/5] init playwright --- Cargo.lock | 84 ++++++++++++++++++++++++++++++++++------- Cargo.toml | 4 +- executor/Cargo.toml | 8 ++++ executor/src/main.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 executor/Cargo.toml create mode 100644 executor/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index a913814e4..9125cb703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,7 +365,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.59.0", @@ -774,7 +774,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -1337,10 +1337,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1886,6 +1898,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" + [[package]] name = "litemap" version = "0.7.4" @@ -1957,7 +1975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2303,7 +2321,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 0.38.42", "tracing", "windows-sys 0.59.0", ] @@ -2403,7 +2421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", - "getrandom", + "getrandom 0.2.15", "rand", "ring", "rustc-hash", @@ -2472,7 +2490,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -2626,7 +2644,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -2758,7 +2776,20 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.2", "windows-sys 0.59.0", ] @@ -3239,14 +3270,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", - "rustix", + "rustix 1.0.2", "windows-sys 0.59.0", ] @@ -3721,7 +3753,7 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ - "getrandom", + "getrandom 0.2.15", "serde", ] @@ -3767,6 +3799,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.93" @@ -3879,6 +3920,14 @@ dependencies = [ "url", ] +[[package]] +name = "web-prover-executor" +version = "0.1.0" +dependencies = [ + "tempfile", + "uuid", +] + [[package]] name = "web-prover-notary" version = "0.7.0" @@ -4112,6 +4161,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index f0ab4aabe..ac2fb4312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members =["client", "notary", "core", "tests"] +members =["client", "notary", "core", "tests", "executor"] resolver="2" [workspace.dependencies] @@ -38,6 +38,8 @@ uuid ={ version="1.10.0", default-features=false, features=["v4", "serde"] tracing-test="0.2" +tempfile="3.18.0" + [profile.dev] incremental =true opt-level =1 diff --git a/executor/Cargo.toml b/executor/Cargo.toml new file mode 100644 index 000000000..294c45bc0 --- /dev/null +++ b/executor/Cargo.toml @@ -0,0 +1,8 @@ +[package] +edition="2021" +name ="web-prover-executor" +version="0.1.0" + +[dependencies] +tempfile={ workspace=true } +uuid ={ workspace=true } diff --git a/executor/src/main.rs b/executor/src/main.rs new file mode 100644 index 000000000..d89c29151 --- /dev/null +++ b/executor/src/main.rs @@ -0,0 +1,89 @@ +use std::{io::Write, process::Stdio, time::Duration}; + +use tempfile::NamedTempFile; +use uuid::Uuid; + +/// The Playwright template with a placeholder for the script +const PLAYWRIGHT_TEMPLATE: &str = r#" +const { chromium } = require('playwright-core'); +const { prompt, prove, setSessionUUID } = require("@plutoxyz/playwright-utils"); + +(async () => { + const sessionUUID = process.argv[2]; + setSessionUUID(sessionUUID); + console.log("Starting Playwright session with UUID:", sessionUUID); + + const browser = await chromium.launch({ + headless: true, + executablePath: '/Users/darkrai/Library/Caches/ms-playwright/chromium_headless_shell-1155/chrome-mac/headless_shell' + }); + const context = await browser.newContext(); + const page = await context.newPage(); + + // Developer provided script: + {{.Script}} + + await browser.close(); +})(); +"#; + +fn run_playwright_script(script: &str) -> Result<(), Box> { + let filled_template = PLAYWRIGHT_TEMPLATE.replace("{{.Script}}", script); + + // Generate a session UUID + let session_uuid = Uuid::new_v4().to_string(); + + let mut temp_file = NamedTempFile::new()?; + let temp_path = temp_file.path().to_owned(); + + temp_file.write_all(filled_template.as_bytes())?; + + // close the file to flush the buffer + let _temp_file = temp_file.into_temp_path(); + + // Execute the command with timeout + println!("Starting Playwright session with UUID: {}", session_uuid); + let mut command = std::process::Command::new("node"); + let mut child = command + .arg(temp_path) + .arg(session_uuid.clone()) + .env("DEBUG", "pw:api") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + // Set a timeout of 20 seconds (matching the Go version) + // let timeout = Duration::from_secs(20); + // // kill process after timeout + // let _ = std::thread::spawn(move || { + // std::thread::sleep(timeout); + // let _ = child.kill(); + // }); + + let output = child.wait_with_output()?; + println!("Output: {:?}", output); + + // Convert output to string + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + + println!("Stdout: {}", stdout); + println!("Stderr: {}", stderr); + + Ok(()) +} + +fn main() { + println!("Hello, world!"); + // Example developer script to inject + let developer_script = r#" + await page.goto('https://example.com'); + console.log('Page title:', await page.title()); + + // Take a screenshot + await page.screenshot({ path: 'example.png' }); + console.log('Screenshot taken'); + "#; + + let _ = run_playwright_script(developer_script); +} From ec8a04327ecf87c9ba41c892728707a53ceae45f Mon Sep 17 00:00:00 2001 From: lonerapier Date: Tue, 11 Mar 2025 18:14:10 +0530 Subject: [PATCH 2/5] basic script working --- README.md | 4 ++-- executor/README.md | 26 ++++++++++++++++++++++ executor/src/main.rs | 31 +++++++++++++++++++++++++-- notary/src/main.rs | 3 +++ notary/src/runner.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 executor/README.md create mode 100644 notary/src/runner.rs diff --git a/README.md b/README.md index f1d6a0034..129cb84c1 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ If you have any questions, please reach out to any of Pluto's [team members](htt ### Usage ``` -cargo run -p notary -- --config ./fixture/notary-config.toml -cargo run -p client -- --config ./fixture/client.proxy.json +cargo run -p web-prover-notary -- --config ./fixture/notary-config.toml +cargo run -p web-prover-client -- --config ./fixture/client.proxy.json ``` ## Security Status diff --git a/executor/README.md b/executor/README.md new file mode 100644 index 000000000..27f22adb0 --- /dev/null +++ b/executor/README.md @@ -0,0 +1,26 @@ +# Web Prover Executor + +## Set up playground + +``` +npx playwright install +npm install -g playwright-core + +git clone git@github.com:pluto/playwright-playground.git +cd playwright-playground +npm install -g ./playwright-utils + +export NODE_PATH=$(npm root -g) +``` + +## Run example + +Run notary in a separate terminal: +``` +RUST_LOG=debug cargo run -p web-prover-notary -- --config ./fixture/notary-config.toml +``` + +Run example executor: +``` +cargo run -p web-prover-executor +``` \ No newline at end of file diff --git a/executor/src/main.rs b/executor/src/main.rs index d89c29151..a75dcf5f5 100644 --- a/executor/src/main.rs +++ b/executor/src/main.rs @@ -1,4 +1,4 @@ -use std::{io::Write, process::Stdio, time::Duration}; +use std::{io::Write, process::Stdio}; use tempfile::NamedTempFile; use uuid::Uuid; @@ -73,8 +73,34 @@ fn run_playwright_script(script: &str) -> Result<(), Box> Ok(()) } +const DEVELOPER_SCRIPT: &str = r#" +await page.goto("https://pseudo-bank.pluto.dev"); + +const username = page.getByRole("textbox", { name: "Username" }); +const password = page.getByRole("textbox", { name: "Password" }); + +let input = await prompt([ + { title: "Username", types: "text" }, + { title: "Password", types: "password" }, +]); + +await username.fill(input.inputs[0]); +await password.fill(input.inputs[1]); + +const loginBtn = page.getByRole("button", { name: "Login" }); +await loginBtn.click(); + +await page.waitForSelector("text=Your Accounts", { timeout: 5000 }); + +const balanceLocator = page.locator("\#balance-2"); +await balanceLocator.waitFor({ state: "visible", timeout: 5000 }); +const balanceText = (await balanceLocator.textContent()) || ""; +const balance = parseFloat(balanceText.replace(/[$,]/g, "")); + +await prove("bank_balance", balance); +"#; + fn main() { - println!("Hello, world!"); // Example developer script to inject let developer_script = r#" await page.goto('https://example.com'); @@ -84,6 +110,7 @@ fn main() { await page.screenshot({ path: 'example.png' }); console.log('Screenshot taken'); "#; + let developer_script = DEVELOPER_SCRIPT; let _ = run_playwright_script(developer_script); } diff --git a/notary/src/main.rs b/notary/src/main.rs index 1491f4a83..7e750d217 100644 --- a/notary/src/main.rs +++ b/notary/src/main.rs @@ -31,6 +31,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod config; mod error; mod proxy; +mod runner; mod verifier; struct SharedState { @@ -90,6 +91,8 @@ async fn main() -> Result<(), NotaryServerError> { let router = Router::new() .route("/health", get(|| async move { (StatusCode::OK, "Ok").into_response() })) .route("/v1/proxy", post(proxy::proxy)) + .route("/v1/prompt", post(runner::prompt)) + .route("/v1/prove", post(runner::prove)) .route("/v1/meta/keys/:key", get(meta_keys)) .layer(CorsLayer::permissive()) .with_state(shared_state); diff --git a/notary/src/runner.rs b/notary/src/runner.rs new file mode 100644 index 000000000..3e8c4a369 --- /dev/null +++ b/notary/src/runner.rs @@ -0,0 +1,51 @@ +use std::sync::Arc; + +use axum::{ + extract::{self, State}, + Json, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::{error::NotaryServerError, SharedState}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Prompt { + pub title: String, + pub types: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PromptRequest { + pub uuid: String, + pub prompts: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PromptResponse { + pub inputs: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ProveRequest { + pub uuid: String, + pub key: String, + pub value: Value, +} + +pub async fn prompt( + State(state): State>, + extract::Json(payload): extract::Json, +) -> Result, NotaryServerError> { + let inputs = payload.prompts.iter().map(|prompt| prompt.title.clone()).collect(); + let response = PromptResponse { inputs }; + Ok(Json(response)) +} + +pub async fn prove( + State(state): State>, + extract::Json(payload): extract::Json, +) -> Result, NotaryServerError> { + println!("Proving: {:?}", payload); + Ok(Json(())) +} From 9422cdd039f4775f031ee4a04bfbabba290fc25c Mon Sep 17 00:00:00 2001 From: lonerapier Date: Wed, 12 Mar 2025 01:08:31 +0530 Subject: [PATCH 3/5] add playwright config and timeout --- Cargo.lock | 6 +- Cargo.toml | 3 +- executor/Cargo.toml | 6 +- executor/src/lib.rs | 1 + executor/src/main.rs | 116 ----------------------- executor/src/playwright.rs | 188 +++++++++++++++++++++++++++++++++++++ 6 files changed, 199 insertions(+), 121 deletions(-) create mode 100644 executor/src/lib.rs delete mode 100644 executor/src/main.rs create mode 100644 executor/src/playwright.rs diff --git a/Cargo.lock b/Cargo.lock index 9125cb703..959acbb39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3777,9 +3777,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -3925,7 +3925,9 @@ name = "web-prover-executor" version = "0.1.0" dependencies = [ "tempfile", + "tracing", "uuid", + "wait-timeout", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ac2fb4312..29cac4711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,8 @@ uuid ={ version="1.10.0", default-features=false, features=["v4", "serde"] tracing-test="0.2" -tempfile="3.18.0" +tempfile ="3.18.0" +wait-timeout="0.2.1" [profile.dev] incremental =true diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 294c45bc0..536379fc1 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -4,5 +4,7 @@ name ="web-prover-executor" version="0.1.0" [dependencies] -tempfile={ workspace=true } -uuid ={ workspace=true } +tempfile ={ workspace=true } +tracing ={ workspace=true } +uuid ={ workspace=true } +wait-timeout={ workspace=true } diff --git a/executor/src/lib.rs b/executor/src/lib.rs new file mode 100644 index 000000000..6d6e1590d --- /dev/null +++ b/executor/src/lib.rs @@ -0,0 +1 @@ +mod playwright; diff --git a/executor/src/main.rs b/executor/src/main.rs deleted file mode 100644 index a75dcf5f5..000000000 --- a/executor/src/main.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::{io::Write, process::Stdio}; - -use tempfile::NamedTempFile; -use uuid::Uuid; - -/// The Playwright template with a placeholder for the script -const PLAYWRIGHT_TEMPLATE: &str = r#" -const { chromium } = require('playwright-core'); -const { prompt, prove, setSessionUUID } = require("@plutoxyz/playwright-utils"); - -(async () => { - const sessionUUID = process.argv[2]; - setSessionUUID(sessionUUID); - console.log("Starting Playwright session with UUID:", sessionUUID); - - const browser = await chromium.launch({ - headless: true, - executablePath: '/Users/darkrai/Library/Caches/ms-playwright/chromium_headless_shell-1155/chrome-mac/headless_shell' - }); - const context = await browser.newContext(); - const page = await context.newPage(); - - // Developer provided script: - {{.Script}} - - await browser.close(); -})(); -"#; - -fn run_playwright_script(script: &str) -> Result<(), Box> { - let filled_template = PLAYWRIGHT_TEMPLATE.replace("{{.Script}}", script); - - // Generate a session UUID - let session_uuid = Uuid::new_v4().to_string(); - - let mut temp_file = NamedTempFile::new()?; - let temp_path = temp_file.path().to_owned(); - - temp_file.write_all(filled_template.as_bytes())?; - - // close the file to flush the buffer - let _temp_file = temp_file.into_temp_path(); - - // Execute the command with timeout - println!("Starting Playwright session with UUID: {}", session_uuid); - let mut command = std::process::Command::new("node"); - let mut child = command - .arg(temp_path) - .arg(session_uuid.clone()) - .env("DEBUG", "pw:api") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - // Set a timeout of 20 seconds (matching the Go version) - // let timeout = Duration::from_secs(20); - // // kill process after timeout - // let _ = std::thread::spawn(move || { - // std::thread::sleep(timeout); - // let _ = child.kill(); - // }); - - let output = child.wait_with_output()?; - println!("Output: {:?}", output); - - // Convert output to string - let stdout = String::from_utf8_lossy(&output.stdout).to_string(); - let stderr = String::from_utf8_lossy(&output.stderr).to_string(); - - println!("Stdout: {}", stdout); - println!("Stderr: {}", stderr); - - Ok(()) -} - -const DEVELOPER_SCRIPT: &str = r#" -await page.goto("https://pseudo-bank.pluto.dev"); - -const username = page.getByRole("textbox", { name: "Username" }); -const password = page.getByRole("textbox", { name: "Password" }); - -let input = await prompt([ - { title: "Username", types: "text" }, - { title: "Password", types: "password" }, -]); - -await username.fill(input.inputs[0]); -await password.fill(input.inputs[1]); - -const loginBtn = page.getByRole("button", { name: "Login" }); -await loginBtn.click(); - -await page.waitForSelector("text=Your Accounts", { timeout: 5000 }); - -const balanceLocator = page.locator("\#balance-2"); -await balanceLocator.waitFor({ state: "visible", timeout: 5000 }); -const balanceText = (await balanceLocator.textContent()) || ""; -const balance = parseFloat(balanceText.replace(/[$,]/g, "")); - -await prove("bank_balance", balance); -"#; - -fn main() { - // Example developer script to inject - let developer_script = r#" - await page.goto('https://example.com'); - console.log('Page title:', await page.title()); - - // Take a screenshot - await page.screenshot({ path: 'example.png' }); - console.log('Screenshot taken'); - "#; - let developer_script = DEVELOPER_SCRIPT; - - let _ = run_playwright_script(developer_script); -} diff --git a/executor/src/playwright.rs b/executor/src/playwright.rs new file mode 100644 index 000000000..bc4e6e666 --- /dev/null +++ b/executor/src/playwright.rs @@ -0,0 +1,188 @@ +use std::{ + error::Error, + io::{Read, Write}, + path::PathBuf, + process::{Command, Stdio}, + time::Duration, +}; + +use tempfile::NamedTempFile; +use tracing::{debug, error}; +use uuid::Uuid; +use wait_timeout::ChildExt; + +/// The Playwright template with a placeholder for the script +const PLAYWRIGHT_TEMPLATE: &str = r#" +const { chromium } = require('playwright-core'); +const { prompt, prove, setSessionUUID } = require("@plutoxyz/playwright-utils"); + +(async () => { + const sessionUUID = process.argv[2]; + setSessionUUID(sessionUUID); + console.log("Starting Playwright session with UUID:", sessionUUID); + + const browser = await chromium.launch({ + headless: true, + executablePath: '/Users/darkrai/Library/Caches/ms-playwright/chromium_headless_shell-1155/chrome-mac/headless_shell' + }); + const context = await browser.newContext(); + const page = await context.newPage(); + + // Developer provided script: + {{.Script}} + + await browser.close(); +})(); +"#; + +/// Configuration for the Playwright runner +pub struct PlaywrightRunnerConfig { + /// Developer script to run in the Playwright template + script: String, + /// Timeout for script execution in seconds + pub timeout_seconds: u64, +} + +pub struct PlaywrightRunner { + /// scipt template with placeholder for the developer script + template: String, + /// Playwright runner configuration + config: PlaywrightRunnerConfig, + /// Path to the Node.js executable + node_path: PathBuf, +} + +#[derive(Debug)] +pub struct PlaywrightOutput { + pub stdout: String, + pub stderr: String, +} + +impl PlaywrightRunner { + pub fn new(config: PlaywrightRunnerConfig, template: String, node_path: PathBuf) -> Self { + Self { config, template, node_path } + } + + pub fn run_script(&self, session_id: Uuid) -> Result> { + // fill the template with the developer script + let template = self.template.replace("{{.Script}}", &self.config.script); + + // create a temporary file to store the template + let mut temp_file = NamedTempFile::new()?; + temp_file.write_all(template.as_bytes())?; + let temp_path = temp_file.path().to_owned(); + let temp_dir = temp_path.parent().unwrap(); + + // close the file to flush the buffer + let _temp_file = temp_file.into_temp_path(); + + // Execute the command with timeout + debug!("Starting Playwright session id: {}", session_id); + let mut command = Command::new(&self.node_path); + let mut child = command + .arg(&temp_path) + .arg(session_id.to_string()) + .env("DEBUG", "pw:api") + .current_dir(temp_dir) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + // Set a timeout + let timeout = Duration::from_secs(self.config.timeout_seconds); + let _ = match child.wait_timeout(timeout)? { + Some(status) => + if let Some(code) = status.code() { + code + } else { + error!("Process terminated by signal: {:?}", status); + return Err("Process terminated by signal".into()); + }, + None => { + child.kill()?; + error!("Process timed out after {:?}", timeout); + return Err("Process timed out".into()); + }, + }; + + // Convert output to string + let stdout = match child.stdout.take() { + Some(mut stdout_stream) => { + let mut stdout = String::new(); + stdout_stream.read_to_string(&mut stdout)?; + stdout + }, + None => String::new(), + }; + + let stderr = match child.stderr.take() { + Some(mut stderr_stream) => { + let mut stderr = String::new(); + stderr_stream.read_to_string(&mut stderr)?; + stderr + }, + None => String::new(), + }; + + let output = PlaywrightOutput { stdout, stderr }; + + Ok(output) + } +} + +mod tests { + + use super::*; + + const EXAMPLE_DEVELOPER_SCRIPT: &str = r#" +await page.goto("https://pseudo-bank.pluto.dev"); + +const username = page.getByRole("textbox", { name: "Username" }); +const password = page.getByRole("textbox", { name: "Password" }); + +let input = await prompt([ + { title: "Username", types: "text" }, + { title: "Password", types: "password" }, +]); + +await username.fill(input.inputs[0]); +await password.fill(input.inputs[1]); + +const loginBtn = page.getByRole("button", { name: "Login" }); +await loginBtn.click(); + +await page.waitForSelector("text=Your Accounts", { timeout: 5000 }); + +const balanceLocator = page.locator("\#balance-2"); +await balanceLocator.waitFor({ state: "visible", timeout: 5000 }); +const balanceText = (await balanceLocator.textContent()) || ""; +const balance = parseFloat(balanceText.replace(/[$,]/g, "")); + +await prove("bank_balance", balance); +"#; + + #[test] + fn test_playwright_script() { + // Example developer script to inject into the Playwright template + let session_id = Uuid::new_v4(); + // output of `which node` + let node_path = + Command::new("which").arg("node").output().expect("Failed to run `which node`").stdout; + let node_path = String::from_utf8_lossy(&node_path).trim().to_string(); + + let config = PlaywrightRunnerConfig { + script: EXAMPLE_DEVELOPER_SCRIPT.to_string(), + timeout_seconds: 30, + }; + let runner = + PlaywrightRunner::new(config, PLAYWRIGHT_TEMPLATE.to_string(), PathBuf::from(node_path)); + + let result = runner.run_script(session_id); + + if let Err(e) = result { + eprintln!("Failed to run Playwright script: {:?}", e); + } else { + println!("output: {:?}", result.unwrap()); + } + } +} From 266c5609fc1d3135bd9bf6b6e23938b8aa9bda31 Mon Sep 17 00:00:00 2001 From: lonerapier Date: Wed, 12 Mar 2025 01:08:40 +0530 Subject: [PATCH 4/5] start internal listener --- notary/src/config.rs | 2 ++ notary/src/main.rs | 14 ++++++++++++-- notary/src/runner.rs | 13 ++++++------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/notary/src/config.rs b/notary/src/config.rs index b399ff171..33f2505b1 100644 --- a/notary/src/config.rs +++ b/notary/src/config.rs @@ -15,6 +15,7 @@ pub struct Config { pub server_cert: String, pub server_key: String, pub listen: String, + pub listen_internal: String, pub notary_signing_key: String, pub acme_email: String, pub acme_domain: String, @@ -27,6 +28,7 @@ pub fn read_config() -> Config { let builder = config::Config::builder() // TODO is this the right way to make server_cert optional? .set_default("listen", "0.0.0.0:443").unwrap() + .set_default("listen_internal", "127.0.0.1:7935").unwrap() .set_default("server_cert", "").unwrap() .set_default("server_key", "").unwrap() .set_default("notary_signing_key", "").unwrap() diff --git a/notary/src/main.rs b/notary/src/main.rs index 7e750d217..dbdc3e840 100644 --- a/notary/src/main.rs +++ b/notary/src/main.rs @@ -91,12 +91,22 @@ async fn main() -> Result<(), NotaryServerError> { let router = Router::new() .route("/health", get(|| async move { (StatusCode::OK, "Ok").into_response() })) .route("/v1/proxy", post(proxy::proxy)) - .route("/v1/prompt", post(runner::prompt)) - .route("/v1/prove", post(runner::prove)) .route("/v1/meta/keys/:key", get(meta_keys)) .layer(CorsLayer::permissive()) .with_state(shared_state); + // Create a separate internal router for prompts + // and Start the internal HTTP server as a separate task + let internal_router = + Router::new().route("/prompt", post(runner::prompt)).route("/prove", post(runner::prove)); + let internal_listener = TcpListener::bind(&c.listen_internal).await?; + info!("Internal server listening on http://{}", &c.listen_internal); + tokio::spawn(async move { + if let Err(e) = axum::serve(internal_listener, internal_router).await { + error!("Internal server error: {:?}", e); + } + }); + if !c.server_cert.is_empty() || !c.server_key.is_empty() { let _ = listen(listener, router, &c.server_cert, &c.server_key).await; } else { diff --git a/notary/src/runner.rs b/notary/src/runner.rs index 3e8c4a369..e8b6f37a9 100644 --- a/notary/src/runner.rs +++ b/notary/src/runner.rs @@ -1,13 +1,12 @@ -use std::sync::Arc; - use axum::{ - extract::{self, State}, + extract::{self}, Json, }; use serde::{Deserialize, Serialize}; use serde_json::Value; +use tracing::debug; -use crate::{error::NotaryServerError, SharedState}; +use crate::error::NotaryServerError; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Prompt { @@ -34,18 +33,18 @@ pub struct ProveRequest { } pub async fn prompt( - State(state): State>, extract::Json(payload): extract::Json, ) -> Result, NotaryServerError> { + debug!("Prompting: {:?}", payload); let inputs = payload.prompts.iter().map(|prompt| prompt.title.clone()).collect(); let response = PromptResponse { inputs }; + Ok(Json(response)) } pub async fn prove( - State(state): State>, extract::Json(payload): extract::Json, ) -> Result, NotaryServerError> { - println!("Proving: {:?}", payload); + debug!("Proving: {:?}", payload); Ok(Json(())) } From 9bc575796ae44d1a517c77da2783897acc157f20 Mon Sep 17 00:00:00 2001 From: lonerapier Date: Wed, 12 Mar 2025 01:32:52 +0530 Subject: [PATCH 5/5] add env vars --- executor/src/playwright.rs | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/executor/src/playwright.rs b/executor/src/playwright.rs index bc4e6e666..78cb2ae0c 100644 --- a/executor/src/playwright.rs +++ b/executor/src/playwright.rs @@ -50,6 +50,8 @@ pub struct PlaywrightRunner { config: PlaywrightRunnerConfig, /// Path to the Node.js executable node_path: PathBuf, + /// environment variables + env: Vec<(String, String)>, } #[derive(Debug)] @@ -58,9 +60,16 @@ pub struct PlaywrightOutput { pub stderr: String, } +// TODO: add a PlaywrightError type + impl PlaywrightRunner { - pub fn new(config: PlaywrightRunnerConfig, template: String, node_path: PathBuf) -> Self { - Self { config, template, node_path } + pub fn new( + config: PlaywrightRunnerConfig, + template: String, + node_path: PathBuf, + env_vars: Vec<(String, String)>, + ) -> Self { + Self { config, template, node_path, env: env_vars } } pub fn run_script(&self, session_id: Uuid) -> Result> { @@ -79,14 +88,19 @@ impl PlaywrightRunner { // Execute the command with timeout debug!("Starting Playwright session id: {}", session_id); let mut command = Command::new(&self.node_path); - let mut child = command + let command = command .arg(&temp_path) .arg(session_id.to_string()) - .env("DEBUG", "pw:api") .current_dir(temp_dir) .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; + .stderr(Stdio::piped()); + + // Add environment variables + for (key, value) in &self.env { + command.env(key, value); + } + + let mut child = command.spawn()?; // Set a timeout let timeout = Duration::from_secs(self.config.timeout_seconds); @@ -174,8 +188,12 @@ await prove("bank_balance", balance); script: EXAMPLE_DEVELOPER_SCRIPT.to_string(), timeout_seconds: 30, }; - let runner = - PlaywrightRunner::new(config, PLAYWRIGHT_TEMPLATE.to_string(), PathBuf::from(node_path)); + let runner = PlaywrightRunner::new( + config, + PLAYWRIGHT_TEMPLATE.to_string(), + PathBuf::from(node_path), + vec![(String::from("DEBUG"), String::from("pw:api"))], + ); let result = runner.run_script(session_id);