1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::sync::Arc;

use axum::{
    extract::{ws::WebSocket, WebSocketUpgrade, Query},
    http::Uri,
    response::Html,
    routing::{get, post},
    Router, Extension,
};
use html_editor::{
    operation::{Editable, Htmlifiable, Selector},
    Node,
};
use tokio::sync::Mutex;
use serde::Deserialize;

#[derive(Deserialize)]
struct LoadParameter {
    path: String
}

struct SocketState {
    subscriber: Mutex<Vec<WebSocket>>
}

impl SocketState {
    fn new() -> Self {
        Self {
            subscriber: Mutex::new(Vec::default()),
        }
    }
}

pub struct Handler {
     client: reqwest::Client
}

impl Handler {
    fn new() -> Self {
        return Self { client: reqwest::Client::new() }
    }

    pub async fn refresh(&self) -> Result<reqwest::Response, reqwest::Error> {
        return self.client.post("http://localhost:3000/ws/refresh").send().await;
    }

    pub async fn load(&self, path: &str) -> Result<reqwest::Response, reqwest::Error> {
        let url = format!("http://localhost:3000/ws/load?path={}", path);
        dbg!(&url);
        return self.client.post(url).send().await;
    }
}




pub fn listen(directory: String, address: String, port: u32) -> Handler {
    let state = Arc::new(SocketState::new());
    let app: Router = Router::new()
        .route("/ws/connect", get(ws_handler))
        .route("/ws/refresh", post(refresh_handler))
        .route("/ws/load", post(load_handler))
        .layer(Extension(state))
        .fallback(get(|uri: Uri| async move {
            Html(
                fetch_file(uri.path().to_string(), &directory).unwrap()
            )
        }));
   
    let addr = format!("{}:{}", address.replace("localhost", "127.0.0.1"), port);

    tokio::spawn(async move {
        axum::Server::bind(&addr.parse().unwrap())
            .serve(app.into_make_service())
            .await
            .unwrap();
    });

    return Handler::new()
}

async fn ws_handler(
    event: WebSocketUpgrade,
    Extension(state): Extension<Arc<SocketState>>,
) -> axum::response::Response {
    event.on_upgrade(|socket: WebSocket| async move {
        let mut subscriber = state.subscriber.lock().await;
        subscriber.push(socket);
    })
}

async fn refresh_handler(Extension(state): Extension<Arc<SocketState>>) -> String {
    for subscriber in state.subscriber.lock().await.iter_mut() {
        subscriber.send("".into()).await.unwrap();
    }

    "refreshing".to_string()
}

async fn load_handler(Extension(state): Extension<Arc<SocketState>>, paramerter: Query<LoadParameter>) -> String {
    for subscriber in state.subscriber.lock().await.iter_mut() {
        subscriber.send(paramerter.path.clone().into()).await.unwrap();
    }

    "loading".to_string()
}

fn fetch_file(url: String, directory: &String) -> Result<String, &str> {
    let path = format!("{}{}", directory, url_to_path(&url));
    dbg!(&path);
    let file_optional = read_file(path);
    let Ok(file) = file_optional else {
        return Err("not found");
    };

    let injected_file = inject_websocket_into(file.clone());
    return Ok(injected_file);
}

fn url_to_path(url: &str) -> String {
    let with_index_default = if url.ends_with("/") {
        format!("{}index", url)
    } else {
        url.to_string()
    };

    let with_default_extension = if !with_index_default.contains(".") {
        return format!("{}.html", with_index_default);
    } else {
        with_index_default
    };

    return with_default_extension.to_string();
}

fn read_file(path: String) -> Result<String, std::io::Error> {
    return std::fs::read_to_string(path);
}

fn inject_websocket_into(content: String) -> String {
    let ws_file = include_str!("../res/ws.js");
    let mut dom = html_editor::parse(&content).unwrap();
    let text_script_node = Node::Text(ws_file.to_string());
    let script_node = Node::new_element("script", vec![], vec![text_script_node]);
    dom.insert_to(&Selector::from("body"), script_node);
    return dom.html();
}