tauri_plugin_playwright/
lib.rs1use std::collections::HashMap;
2use std::sync::Arc;
3use tokio::sync::Mutex;
4
5use tauri::{
6 plugin::{Builder, TauriPlugin},
7 Runtime,
8};
9
10mod commands;
11mod native_capture;
12mod server;
13
14use server::PendingResults;
15
16fn js_init_script() -> String {
17 let mut js = String::new();
21 js.push_str("(function() {\n");
22 js.push_str(" function inject() {\n");
23 js.push_str(" if (document.getElementById('__pw_script__')) return;\n");
24 js.push_str(" var s = document.createElement('script');\n");
25 js.push_str(" s.id = '__pw_script__';\n");
26 js.push_str(" s.textContent = '");
27 let poll_script = concat!(
29 "(function() {",
30 " if (window.__PW_ACTIVE__) return;",
31 " window.__PW_ACTIVE__ = true;",
32 " async function poll() {",
33 " while (window.__PW_ACTIVE__) {",
34 " try {",
35 " var resp = await fetch(\"/pw-poll\");",
36 " if (resp.status === 200) {",
37 " var cmd = await resp.json();",
38 " if (cmd && cmd.id && cmd.script) {",
39 " try {",
40 " var fn = new Function(\"return (async function() { return (\" + cmd.script + \"); })()\");",
41 " var result = await fn();",
42 " var body = JSON.stringify({ id: cmd.id, result: JSON.stringify({ ok: true, v: result }) });",
43 " await fetch(\"/pw\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: body });",
44 " } catch(e) {",
45 " var body = JSON.stringify({ id: cmd.id, result: JSON.stringify({ ok: false, e: (e && e.message) || String(e) }) });",
46 " await fetch(\"/pw\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: body }).catch(function(){});",
47 " }",
48 " }",
49 " }",
50 " } catch(e) {}",
51 " await new Promise(function(r) { setTimeout(r, 16); });",
52 " }",
53 " }",
54 " poll();",
55 " console.log(\"[tauri-plugin-playwright] bridge active\");",
56 "})();"
57 );
58 let escaped = poll_script
60 .replace('\\', "\\\\")
61 .replace('\'', "\\'");
62 js.push_str(&escaped);
63 js.push_str("';\n");
64 js.push_str(" document.head.appendChild(s);\n");
65 js.push_str(" }\n");
66 js.push_str(" if (document.head) { inject(); }\n");
67 js.push_str(" else { document.addEventListener('DOMContentLoaded', inject); }\n");
68 js.push_str(" new MutationObserver(function() { if (document.head && !document.getElementById('__pw_script__')) inject(); }).observe(document, { childList: true, subtree: true });\n");
69 js.push_str("})();\n");
70 js
71}
72
73pub fn init<R: Runtime>() -> TauriPlugin<R> {
74 init_with_config(PluginConfig::default())
75}
76
77pub fn init_with_config<R: Runtime>(config: PluginConfig) -> TauriPlugin<R> {
78 let pending: PendingResults = Arc::new(Mutex::new(HashMap::new()));
79 let pending_for_setup = Arc::clone(&pending);
80
81 Builder::new("playwright")
82 .js_init_script(js_init_script())
83 .setup(move |app, _api| {
84 server::start(
85 app.clone(),
86 Arc::clone(&pending_for_setup),
87 config.socket_path.clone(),
88 config.tcp_port,
89 );
90 Ok(())
91 })
92 .build()
93}
94
95#[derive(Debug, Clone)]
96pub struct PluginConfig {
97 pub socket_path: Option<String>,
98 pub tcp_port: Option<u16>,
99}
100
101impl Default for PluginConfig {
102 fn default() -> Self {
103 Self {
104 socket_path: Some("/tmp/tauri-playwright.sock".to_string()),
105 tcp_port: None,
106 }
107 }
108}
109
110impl PluginConfig {
111 pub fn new() -> Self { Self::default() }
112 pub fn socket_path(mut self, path: impl Into<String>) -> Self { self.socket_path = Some(path.into()); self }
113 pub fn tcp_port(mut self, port: u16) -> Self { self.tcp_port = Some(port); self }
114}