use actix_files as fs;
use actix_send_websocket::WebSocket;
use actix_web::web::Data;
use actix_web::{middleware, web, App, HttpResponse, HttpServer, Responder, Result};
use notify::{watcher, RecursiveMode, Watcher};
use std::error::Error;
use std::fmt::Display;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
static UUID: &str = "69046294-8cb0-4f81-970d-b1c2437966cf";
#[derive(Debug)]
struct GeneralError(String);
impl Display for GeneralError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{:?}", self)
}
}
impl Error for GeneralError {}
fn build(path: PathBuf) -> Result<(), GeneralError> {
use wasm_pack::command::build::*;
let build_options = BuildOptions {
path: Some(path),
target: Target::Web,
dev: true,
release: false,
out_dir: "pkg".to_string(),
out_name: Some("entry".to_string()),
..BuildOptions::default()
};
to_general_error(to_general_error(Build::try_from_opts(build_options))?.run())?;
Ok(())
}
fn to_general_error<T>(msg: Result<T, impl std::fmt::Debug>) -> Result<T, GeneralError> {
msg.map_err(|err| GeneralError(format!("{:?}", err)))
}
fn print_error<T>(msg: Result<T, impl std::fmt::Debug>) {
match msg {
Ok(_) => (),
Err(e) => println!("{:?}", e),
}
}
fn watch(build_finished: Chan, path: PathBuf) {
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = watcher(tx, Duration::from_secs(10)).unwrap();
watcher
.watch(path.join("src"), RecursiveMode::Recursive)
.unwrap();
while let Ok(_) = rx.recv() {
match build(path.clone()) {
Ok(()) => {
print_error(build_finished.try_send(&Reload));
}
Err(e) => {
println!("{:?}", e);
}
}
}
}
async fn ws(ws: WebSocket, build_finished: Data<Chan>) -> impl Responder {
let (_, res, t) = ws.into_parts();
let mut a: Chan = build_finished.get_ref().clone();
actix_web::rt::spawn(async move {
while let Some(Reload) = a.recv().await {
print_error(t.text("reload"));
}
});
res
}
async fn index() -> HttpResponse {
HttpResponse::Ok().content_type("text/html").body(
"
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>🔥 wasm server</title>
<script>
let timeout = 1000;
const open = () => {
const ws = new WebSocket('ws://localhost:8080/ws-69046294-8cb0-4f81-970d-b1c2437966cf');
ws.onopen = () => {timeout = 1000;};
ws.onmessage = (event) => window.location.reload(false);
ws.onclose = () => setTimeout(open);
ws.onerror = () => setTimeout(open, timeout);
timeout *= 1.5;
}
open();
</script>
<script type='module'>
import init from './entry.js';
init().then((m) => m.main());
</script>
</head>
<body></body>
</html>
",
)
}
#[derive(Copy, Clone)]
struct Reload;
type Chan = broadcaster::BroadcastChannel<
Reload,
futures_channel::mpsc::Sender<Reload>,
futures_channel::mpsc::Receiver<Reload>,
>;
fn get_path() -> PathBuf {
let mut args = std::env::args();
args.next();
match args.next() {
Some(path) => {
println!("path: {}", path);
let mut p = PathBuf::new();
p.push(path);
p
}
None => std::env::current_dir().unwrap(),
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let path = get_path();
let chan = broadcaster::BroadcastChannel::with_cap(16);
let path2 = path.clone();
let chan2 = chan.clone();
thread::Builder::new()
.name("file watcher".to_string())
.spawn(move || {
watch(chan, path);
})?;
println!("running on http://127.0.0.1:8080");
HttpServer::new(move || {
App::new()
.data(chan2.clone())
.wrap(middleware::DefaultHeaders::new().header("Cache-Control", "no-cache"))
.route("/", web::get().to(index))
.route(format!("/ws-{}", UUID).as_str(), web::get().to(ws))
.service(fs::Files::new("/", path2.join("pkg")))
})
.bind("127.0.0.1:8080")?
.run()
.await?;
Ok(())
}