dioxus_liveview/
lib.rs

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
148
149
150
151
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]

mod adapters;
#[allow(unused_imports)]
pub use adapters::*;

mod element;
pub mod pool;
mod query;
use dioxus_interpreter_js::NATIVE_JS;
use futures_util::{SinkExt, StreamExt};
pub use pool::*;
mod config;
mod document;
mod events;
mod history;
pub use config::*;
#[cfg(feature = "axum")]
pub mod launch;

pub trait WebsocketTx: SinkExt<String, Error = LiveViewError> {}
impl<T> WebsocketTx for T where T: SinkExt<String, Error = LiveViewError> {}

pub trait WebsocketRx: StreamExt<Item = Result<String, LiveViewError>> {}
impl<T> WebsocketRx for T where T: StreamExt<Item = Result<String, LiveViewError>> {}

#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum LiveViewError {
    #[error("Sending to client error")]
    SendingFailed,
}

fn handle_edits_code() -> String {
    use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;

    let serialize_file_uploads = r#"if (
        target.tagName === "INPUT" &&
        (event.type === "change" || event.type === "input")
      ) {
        const type = target.getAttribute("type");
        if (type === "file") {
          async function read_files() {
            const files = target.files;
            const file_contents = {};

            for (let i = 0; i < files.length; i++) {
              const file = files[i];

              file_contents[file.name] = Array.from(
                new Uint8Array(await file.arrayBuffer())
              );
            }
            let file_engine = {
              files: file_contents,
            };
            contents.files = file_engine;

            if (realId === null) {
              return;
            }
            const message = window.interpreter.sendSerializedEvent({
              name: name,
              element: parseInt(realId),
              data: contents,
              bubbles,
            });
            window.ipc.postMessage(message);
          }
          read_files();
          return;
        }
      }"#;
    let mut interpreter = format!(
        r#"
    // Bring the sledgehammer code
    {SLEDGEHAMMER_JS}

    // And then extend it with our native bindings
    {NATIVE_JS}
    "#
    )
    .replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads)
    .replace("export", "");
    while let Some(import_start) = interpreter.find("import") {
        let import_end = interpreter[import_start..]
            .find([';', '\n'])
            .map(|i| i + import_start)
            .unwrap_or_else(|| interpreter.len());
        interpreter.replace_range(import_start..import_end, "");
    }
    let main_js = include_str!("./main.js");
    let js = format!("{interpreter}\n{main_js}");
    js
}

/// This script that gets injected into your app connects this page to the websocket endpoint
///
/// Once the endpoint is connected, it will send the initial state of the app, and then start
/// processing user events and returning edits to the liveview instance.
///
/// You can pass a relative path prefixed with "/", or enter a full URL including the protocol
/// (`ws:` or `wss:`) as an argument.
///
/// If you enter a relative path, the web client automatically prefixes the host address in
/// `window.location` when creating a web socket to LiveView.
///
/// ```rust
/// use dioxus_liveview::interpreter_glue;
///
/// // Creates websocket connection to same host as current page
/// interpreter_glue("/api/liveview");
///
/// // Creates websocket connection to specified url
/// interpreter_glue("ws://localhost:8080/api/liveview");
/// ```
pub fn interpreter_glue(url_or_path: &str) -> String {
    // If the url starts with a `/`, generate glue which reuses current host
    let get_ws_url = if url_or_path.starts_with('/') {
        r#"
  let loc = window.location;
  let new_url = "";
  if (loc.protocol === "https:") {{
      new_url = "wss:";
  }} else {{
      new_url = "ws:";
  }}
  new_url += "//" + loc.host + path;
  return new_url;
      "#
    } else {
        "return path;"
    };

    let handle_edits = handle_edits_code();

    format!(
        r#"
<script>
    function __dioxusGetWsUrl(path) {{
      {get_ws_url}
    }}

    var WS_ADDR = __dioxusGetWsUrl("{url_or_path}");
    {handle_edits}
</script>
    "#
    )
}