Refactor, use websockify instead of self-backend server
This commit is contained in:
parent
6b10b4bc74
commit
940b71eba2
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
|||||||
/target/
|
/target/
|
||||||
/build/
|
/build/
|
||||||
Cargo.lock
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "axum-websockify"]
|
||||||
|
path = axum-websockify
|
||||||
|
url = https://github.com/HsuJv/axum-websockify.git
|
@ -1,6 +0,0 @@
|
|||||||
[workspace]
|
|
||||||
|
|
||||||
members = [
|
|
||||||
"backend",
|
|
||||||
"frontend",
|
|
||||||
]
|
|
@ -1,11 +1,32 @@
|
|||||||
[tasks.install-debug]
|
[tasks.install-debug]
|
||||||
dependencies = ["build-debug", "member_flow"]
|
dependencies = ["websockify", "wasm-debug"]
|
||||||
|
|
||||||
[tasks.install-release]
|
[tasks.install-release]
|
||||||
dependencies = ["build-release", "member_flow"]
|
dependencies = ["websockify", "wasm-release"]
|
||||||
|
|
||||||
[tasks.member_flow]
|
[tasks.wasm-debug]
|
||||||
run_task = { name = "member_flow", fork = true, parallel = true}
|
script = '''
|
||||||
|
cd ${VNC} && cargo make install-debug
|
||||||
|
'''
|
||||||
|
|
||||||
|
[tasks.wasm-release]
|
||||||
|
script = '''
|
||||||
|
cd ${VNC} && cargo make install-release
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
[tasks.websockify]
|
||||||
|
dependencies = ["install-dir"]
|
||||||
|
script = '''
|
||||||
|
cd ${WEBSOCKIFY} && cargo build --release && cp ./target/release/${WEBSOCKIFY} $INSTALL_PATH/
|
||||||
|
'''
|
||||||
|
|
||||||
|
[tasks.install-dir]
|
||||||
|
script = '''
|
||||||
|
mkdir -p $INSTALL_PATH
|
||||||
|
'''
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
INSTALL_PATH= "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/build"
|
INSTALL_PATH= "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/build"
|
||||||
|
WEBSOCKIFY="axum-websockify"
|
||||||
|
VNC="webvnc"
|
@ -1,5 +1,5 @@
|
|||||||
# A Remote Access Gateway
|
# A Remote Access Gateway
|
||||||
* Full-stack project written with Rust / Yew + Actix
|
* Webassembly Terminal Services written with Rust / Yew
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
@ -25,6 +25,3 @@
|
|||||||
|
|
||||||
* RDP Clients:
|
* RDP Clients:
|
||||||
- WIP
|
- WIP
|
||||||
|
|
||||||
* Backend database
|
|
||||||
- WIP
|
|
||||||
|
1
axum-websockify
Submodule
1
axum-websockify
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 74229048831bc6c2d0227de68f5b1644f0d6c3f6
|
@ -1,55 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = [
|
|
||||||
"Jovi Hsu <jv.hsu@outlook.com>"
|
|
||||||
]
|
|
||||||
categories = ["wasm", "web-programming", "sslvpn"]
|
|
||||||
description = ""
|
|
||||||
edition = "2021"
|
|
||||||
keywords = ["yew", "wasm", "wasm-bindgen", "web", "sslvpn"]
|
|
||||||
license = "GPL3"
|
|
||||||
name = "webgateway-be"
|
|
||||||
readme = "README.md"
|
|
||||||
version = "0.1.0"
|
|
||||||
repository = "https://www.github.com/HsuJv/webgateway"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tokio = {version="1.13.0", feature="io-util"}
|
|
||||||
tokio-io = "0.1.13"
|
|
||||||
tokio-core = "0.1.18"
|
|
||||||
tokio-codec = "0.1.2"
|
|
||||||
tokio-util = "0.6.9"
|
|
||||||
|
|
||||||
actix = "0.12.0"
|
|
||||||
actix-session = "0.5.0-beta.3"
|
|
||||||
actix-web = "4.0.0-beta.10"
|
|
||||||
actix-files = "0.6.0-beta.8"
|
|
||||||
actix-web-actors = "4.0.0-beta.7"
|
|
||||||
actix-codec = "0.4"
|
|
||||||
|
|
||||||
urlencoding = "2.1.0"
|
|
||||||
bytes = "1.1.0"
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
trust-dns-resolver = "0.20"
|
|
||||||
rand = "0.8"
|
|
||||||
rustls = "0.20.0"
|
|
||||||
|
|
||||||
futures = "0.3.17"
|
|
||||||
futures-util= "0.3"
|
|
||||||
|
|
||||||
# log systems
|
|
||||||
femme = "1.3"
|
|
||||||
log = "0.4"
|
|
||||||
async-log = "2.0.0"
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
panic = "unwind"
|
|
||||||
opt-level = 0
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
panic = 'abort'
|
|
||||||
codegen-units = 1
|
|
||||||
opt-level = 's'
|
|
||||||
lto = true
|
|
@ -1,21 +0,0 @@
|
|||||||
[tasks.build-debug]
|
|
||||||
command="cargo"
|
|
||||||
args=["build"]
|
|
||||||
|
|
||||||
[tasks.build-release]
|
|
||||||
command="cargo"
|
|
||||||
args=["build", "--release"]
|
|
||||||
|
|
||||||
[tasks.install-debug]
|
|
||||||
dependencies = ["build-debug"]
|
|
||||||
script = '''
|
|
||||||
mkdir -p $INSTALL_PATH
|
|
||||||
cp $CARGO_MAKE_CRATE_TARGET_DIRECTORY/debug/$CARGO_MAKE_CRATE_NAME $INSTALL_PATH
|
|
||||||
'''
|
|
||||||
|
|
||||||
[tasks.install-release]
|
|
||||||
dependencies = ["build-release"]
|
|
||||||
script = '''
|
|
||||||
mkdir -p $INSTALL_PATH
|
|
||||||
cp $CARGO_MAKE_CRATE_TARGET_DIRECTORY/release/$CARGO_MAKE_CRATE_NAME $INSTALL_PATH
|
|
||||||
'''
|
|
@ -1,213 +0,0 @@
|
|||||||
use crate::agent::ws;
|
|
||||||
use actix::prelude::*;
|
|
||||||
use actix_codec::{Decoder, Encoder};
|
|
||||||
use actix_web::web::Bytes;
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io;
|
|
||||||
use tokio::net::{tcp::OwnedWriteHalf, TcpStream};
|
|
||||||
use tokio_util::codec::FramedRead;
|
|
||||||
|
|
||||||
use log::*;
|
|
||||||
|
|
||||||
struct TcpCodec;
|
|
||||||
|
|
||||||
impl Encoder<Bytes> for TcpCodec {
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn encode(&mut self, _item: Bytes, _dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
// info!("encoding: {:?}", item);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Decoder for TcpCodec {
|
|
||||||
type Item = Bytes;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
// info!("recv from server: {:?}", src);
|
|
||||||
if src.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let web_bytes = Bytes::from(src.to_vec());
|
|
||||||
src.clear();
|
|
||||||
Ok(Some(web_bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
#[rtype(result = "()")]
|
|
||||||
pub enum AgentMsg {
|
|
||||||
Ready(Addr<ws::WsSession>),
|
|
||||||
SendToServer(Bytes),
|
|
||||||
SendToClient(Bytes),
|
|
||||||
Shutdown,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Agent {
|
|
||||||
id: u32,
|
|
||||||
server_info: String,
|
|
||||||
writer: OwnedWriteHalf,
|
|
||||||
ws_addr: Option<Addr<ws::WsSession>>,
|
|
||||||
pending: Vec<Bytes>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for Agent {
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, _ctx: &mut Self::Context) {
|
|
||||||
info!("Agent {} started", self.id);
|
|
||||||
// ctx.address().do_send(AgentMsg::ReadReady);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<AgentMsg> for Agent {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: AgentMsg, ctx: &mut Context<Self>) -> Self::Result {
|
|
||||||
match msg {
|
|
||||||
AgentMsg::Ready(ws_addr) => {
|
|
||||||
self.ws_addr = Some(ws_addr);
|
|
||||||
info!("Agent {} - Websocket connect ready", self.server_info);
|
|
||||||
for msg in self.pending.drain(..) {
|
|
||||||
self.ws_addr
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.do_send(ws::WsMsg::SendToClient(msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AgentMsg::SendToServer(data) => {
|
|
||||||
let to_send = data.to_vec();
|
|
||||||
self.writer.try_write(&to_send).unwrap();
|
|
||||||
}
|
|
||||||
AgentMsg::SendToClient(data) => {
|
|
||||||
if self.ws_addr.is_some() {
|
|
||||||
self.ws_addr
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.do_send(ws::WsMsg::SendToClient(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AgentMsg::Shutdown => {
|
|
||||||
info!("Agent {} - Shutdown", self.server_info);
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamHandler<Result<Bytes, io::Error>> for Agent {
|
|
||||||
fn handle(&mut self, msg: Result<Bytes, io::Error>, ctx: &mut Context<Self>) {
|
|
||||||
match msg {
|
|
||||||
Ok(data) => {
|
|
||||||
// info!("recv from server: {:?}", data);
|
|
||||||
if self.ws_addr.is_some() {
|
|
||||||
ctx.address().do_send(AgentMsg::SendToClient(data));
|
|
||||||
} else {
|
|
||||||
info!("Websocket session not ready");
|
|
||||||
self.pending.push(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("error: {:?}", err);
|
|
||||||
if self.ws_addr.is_some() {
|
|
||||||
self.ws_addr.as_ref().unwrap().do_send(ws::WsMsg::Close);
|
|
||||||
}
|
|
||||||
ctx.address().do_send(AgentMsg::Shutdown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Agent {
|
|
||||||
pub async fn new(id: u32, target: (String, u16)) -> Option<Addr<Agent>> {
|
|
||||||
let (host, port) = target;
|
|
||||||
let server_info = format!("{}:{}", host, port);
|
|
||||||
info!("connect to server: {}", server_info);
|
|
||||||
let server_stream = TcpStream::connect(&server_info).await;
|
|
||||||
if server_stream.is_err() {
|
|
||||||
info!("connect to server failed: {}", server_info);
|
|
||||||
}
|
|
||||||
let server_stream = server_stream.unwrap();
|
|
||||||
let addr = Agent::create(move |ctx| {
|
|
||||||
let (r, w) = server_stream.into_split();
|
|
||||||
let r = FramedRead::new(r, TcpCodec {});
|
|
||||||
Agent::add_stream(r, ctx);
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
server_info,
|
|
||||||
writer: w,
|
|
||||||
ws_addr: None,
|
|
||||||
pending: vec![],
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Some(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(MessageResponse)]
|
|
||||||
pub enum AgentManagerResult {
|
|
||||||
Success(Addr<Agent>),
|
|
||||||
Failed,
|
|
||||||
NoReturn,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
#[rtype(result = "AgentManagerResult")]
|
|
||||||
pub enum AgentManagerMsg {
|
|
||||||
Add((u32, Addr<Agent>)),
|
|
||||||
Get(u32),
|
|
||||||
Del(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AgentManager {
|
|
||||||
agents: HashMap<u32, Addr<Agent>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AgentManager {
|
|
||||||
pub fn new() -> Addr<Self> {
|
|
||||||
Self {
|
|
||||||
agents: HashMap::new(),
|
|
||||||
}
|
|
||||||
.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for AgentManager {
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, _ctx: &mut Context<Self>) {
|
|
||||||
info!("AgentManager started");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopped(&mut self, _ctx: &mut Context<Self>) {
|
|
||||||
info!("AgentManager stopped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<AgentManagerMsg> for AgentManager {
|
|
||||||
type Result = AgentManagerResult;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: AgentManagerMsg, _ctx: &mut Context<Self>) -> Self::Result {
|
|
||||||
match msg {
|
|
||||||
AgentManagerMsg::Add(addr) => {
|
|
||||||
info!("add agent: {:?}", addr.0);
|
|
||||||
self.agents.insert(addr.0, addr.1);
|
|
||||||
AgentManagerResult::NoReturn
|
|
||||||
}
|
|
||||||
AgentManagerMsg::Get(aid) => {
|
|
||||||
info!("get agent: {}", aid);
|
|
||||||
if let Some(addr) = self.agents.get(&aid) {
|
|
||||||
AgentManagerResult::Success(addr.clone())
|
|
||||||
} else {
|
|
||||||
AgentManagerResult::Failed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AgentManagerMsg::Del(id) => {
|
|
||||||
self.agents.remove(&id);
|
|
||||||
AgentManagerResult::NoReturn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
pub mod agent;
|
|
||||||
pub mod remote;
|
|
||||||
pub mod resolver;
|
|
||||||
pub mod ws;
|
|
@ -1,86 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix_session::Session;
|
|
||||||
use actix_web::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use log::info;
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
use super::agent;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct RemoteInfo {
|
|
||||||
#[serde(default)]
|
|
||||||
host: String,
|
|
||||||
#[serde(default)]
|
|
||||||
ip: String,
|
|
||||||
#[serde(default)]
|
|
||||||
port: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/target/validate")]
|
|
||||||
pub async fn target_validate(
|
|
||||||
req: HttpRequest,
|
|
||||||
params: web::Json<RemoteInfo>,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
let remote = params.into_inner();
|
|
||||||
info!("{:?}", remote);
|
|
||||||
let app_data = req.app_data::<Arc<crate::AppData>>().unwrap();
|
|
||||||
|
|
||||||
match app_data.resolver.lockup(remote.host).await {
|
|
||||||
Some(ipaddr) => {
|
|
||||||
let json = json!({
|
|
||||||
"status": "success",
|
|
||||||
"ip": ipaddr
|
|
||||||
});
|
|
||||||
Ok(HttpResponse::Ok().json(json))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let json = json!({
|
|
||||||
"status": "failed",
|
|
||||||
"message": "Failed to resolve the target name"
|
|
||||||
});
|
|
||||||
Ok(HttpResponse::Ok().json(json))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/target/remote")]
|
|
||||||
pub async fn target_remote(
|
|
||||||
req: HttpRequest,
|
|
||||||
session: Session,
|
|
||||||
params: web::Json<RemoteInfo>,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
let aid = rand::thread_rng().gen::<u32>();
|
|
||||||
let app_data = req.app_data::<Arc<crate::AppData>>().unwrap();
|
|
||||||
let remote = params.into_inner();
|
|
||||||
let agent = agent::Agent::new(aid, (remote.ip, remote.port)).await;
|
|
||||||
|
|
||||||
match agent {
|
|
||||||
Some(addr) => {
|
|
||||||
// add to agent list
|
|
||||||
let _ = app_data
|
|
||||||
.agents
|
|
||||||
.send(agent::AgentManagerMsg::Add((aid, addr)))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// add session, so that the websocket can send message to the agent
|
|
||||||
let _ = session.insert("aid", aid);
|
|
||||||
|
|
||||||
// send response
|
|
||||||
let json = json!({
|
|
||||||
"status": "success",
|
|
||||||
});
|
|
||||||
Ok(HttpResponse::Ok().json(json))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let json = json!({
|
|
||||||
"status": "failed",
|
|
||||||
"message": "Failed to connect to the target"
|
|
||||||
});
|
|
||||||
Ok(HttpResponse::Ok().json(json))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
// use actix::{Actor, Addr, Context, Handler, Message, MessageResponse};
|
|
||||||
|
|
||||||
// use std::net::*;
|
|
||||||
// use trust_dns_resolver::config::*;
|
|
||||||
// use trust_dns_resolver::Resolver;
|
|
||||||
|
|
||||||
// use log::info;
|
|
||||||
|
|
||||||
// #[derive(MessageResponse)]
|
|
||||||
// pub enum ResolveResp {
|
|
||||||
// Success(IpAddr),
|
|
||||||
// Failed,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[derive(Message)]
|
|
||||||
// #[rtype(result = "ResolveResp")]
|
|
||||||
// pub enum ResolveMsg {
|
|
||||||
// Resolve(String),
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub struct DnsResolver {
|
|
||||||
// resolver: Resolver,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Actor for DnsResolver {
|
|
||||||
// type Context = Context<Self>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Handler<ResolveMsg> for DnsResolver {
|
|
||||||
// type Result = ResolveResp;
|
|
||||||
|
|
||||||
// fn handle(&mut self, msg: ResolveMsg, _: &mut Context<Self>) -> Self::Result {
|
|
||||||
// match msg {
|
|
||||||
// ResolveMsg::Resolve(name) => {
|
|
||||||
// if let Ok(response) = self.resolver.lookup_ip(name.clone()) {
|
|
||||||
// if let Some(address) = response.iter().next() {
|
|
||||||
// info!("Resolved {} to {}", name, address);
|
|
||||||
// ResolveResp::Success(address)
|
|
||||||
// } else {
|
|
||||||
// ResolveResp::Failed
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// info!("Failed to resolve {}", name);
|
|
||||||
// ResolveResp::Failed
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl DnsResolver {
|
|
||||||
// pub fn new() -> Addr<Self> {
|
|
||||||
// let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
|
|
||||||
|
|
||||||
// DnsResolver { resolver }.start()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
use trust_dns_resolver::{
|
|
||||||
config::*,
|
|
||||||
name_server::{GenericConnection, GenericConnectionProvider, TokioRuntime},
|
|
||||||
};
|
|
||||||
use trust_dns_resolver::{AsyncResolver, TokioHandle};
|
|
||||||
|
|
||||||
use log::*;
|
|
||||||
|
|
||||||
pub struct DnsResolver {
|
|
||||||
resolver: AsyncResolver<GenericConnection, GenericConnectionProvider<TokioRuntime>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DnsResolver {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let resolver = AsyncResolver::new(
|
|
||||||
ResolverConfig::default(),
|
|
||||||
ResolverOpts::default(),
|
|
||||||
TokioHandle,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Self { resolver }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn lockup(&self, name: String) -> Option<IpAddr> {
|
|
||||||
let lookup = self.resolver.lookup_ip(name.clone());
|
|
||||||
|
|
||||||
if let Ok(response) = lookup.await {
|
|
||||||
if let Some(address) = response.iter().next() {
|
|
||||||
info!("Resolved {} to {}", name, address);
|
|
||||||
Some(address)
|
|
||||||
} else {
|
|
||||||
info!("Failed to resolve {}", name);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info!("Failed to resolve {}", name);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix::ActorContext;
|
|
||||||
use actix::{Actor, Addr, Message, StreamHandler};
|
|
||||||
use actix::{AsyncContext, Handler};
|
|
||||||
use actix_session::Session;
|
|
||||||
use actix_web::web::Bytes;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
|
||||||
use actix_web_actors::ws;
|
|
||||||
use log::*;
|
|
||||||
|
|
||||||
use super::agent::*;
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
#[rtype(result = "()")]
|
|
||||||
pub enum WsMsg {
|
|
||||||
SendToClient(Bytes),
|
|
||||||
Close,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define Websocket actor
|
|
||||||
pub struct WsSession {
|
|
||||||
agent: Addr<Agent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for WsSession {
|
|
||||||
type Context = ws::WebsocketContext<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
// start heartbeats otherwise server will disconnect after 10 seconds
|
|
||||||
self.agent.do_send(AgentMsg::Ready(ctx.address()));
|
|
||||||
info!("Websocket connection is established.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<WsMsg> for WsSession {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: WsMsg, ctx: &mut Self::Context) {
|
|
||||||
match msg {
|
|
||||||
WsMsg::SendToClient(data) => {
|
|
||||||
ctx.binary(data);
|
|
||||||
}
|
|
||||||
WsMsg::Close => {
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for ws::Message message
|
|
||||||
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsSession {
|
|
||||||
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
|
|
||||||
match msg {
|
|
||||||
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
|
|
||||||
Ok(ws::Message::Text(text)) => ctx.text(text),
|
|
||||||
Ok(ws::Message::Binary(bin)) => {
|
|
||||||
self.agent.do_send(AgentMsg::SendToServer(bin));
|
|
||||||
}
|
|
||||||
Ok(ws::Message::Close(_)) => {
|
|
||||||
self.agent.do_send(AgentMsg::Shutdown);
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/ws")]
|
|
||||||
pub async fn ws_index(
|
|
||||||
req: HttpRequest,
|
|
||||||
session: Session,
|
|
||||||
stream: web::Payload,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
let aid = session.get::<u32>("aid").unwrap_or(Some(0)).unwrap();
|
|
||||||
let app_data = req.app_data::<Arc<crate::AppData>>().unwrap();
|
|
||||||
|
|
||||||
let resp = match app_data
|
|
||||||
.agents
|
|
||||||
.send(AgentManagerMsg::Get(aid))
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
{
|
|
||||||
AgentManagerResult::Success(agent) => ws::start(WsSession { agent }, &req, stream),
|
|
||||||
_ => Err(actix_web::error::ErrorInternalServerError(
|
|
||||||
"Agent not found",
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
match &resp {
|
|
||||||
Ok(resp) => info!("{:?}", resp),
|
|
||||||
Err(e) => error!("{:?}", e),
|
|
||||||
}
|
|
||||||
resp
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix::Addr;
|
|
||||||
use actix_files as fs;
|
|
||||||
use actix_session::CookieSession;
|
|
||||||
use actix_web::http::{ContentEncoding, StatusCode};
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
use agent::{agent::AgentManager, resolver::DnsResolver};
|
|
||||||
use log::info;
|
|
||||||
use rand::Rng;
|
|
||||||
use user::auth::Authenticator;
|
|
||||||
|
|
||||||
mod agent;
|
|
||||||
mod user;
|
|
||||||
|
|
||||||
const STATIC_DIR: &str = "./static/";
|
|
||||||
const PAGE_INDEX: &str = "./static/index.html";
|
|
||||||
const PAGE_NOT_FOUND: &str = "./static/p404.html";
|
|
||||||
|
|
||||||
pub struct AppData {
|
|
||||||
// session: CookieSession,
|
|
||||||
resolver: DnsResolver,
|
|
||||||
authenticator: Addr<Authenticator>,
|
|
||||||
agents: Addr<AgentManager>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppData {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
resolver: DnsResolver::new(),
|
|
||||||
authenticator: Authenticator::new(),
|
|
||||||
agents: AgentManager::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AppData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_logger() {
|
|
||||||
let logger = femme::pretty::Logger::new();
|
|
||||||
async_log::Logger::wrap(logger, || 12)
|
|
||||||
.start(log::LevelFilter::Warn)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
async fn index(_: HttpRequest) -> Result<fs::NamedFile> {
|
|
||||||
Ok(fs::NamedFile::open(PAGE_INDEX)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn p404() -> Result<fs::NamedFile> {
|
|
||||||
Ok(fs::NamedFile::open(PAGE_NOT_FOUND)?.set_status_code(StatusCode::NOT_FOUND))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::main]
|
|
||||||
async fn main() -> std::io::Result<()> {
|
|
||||||
setup_logger();
|
|
||||||
|
|
||||||
info!("Server starts at http://127.0.0.1:8080");
|
|
||||||
let private_key = rand::thread_rng().gen::<[u8; 32]>();
|
|
||||||
let app_data = Arc::new(AppData::new());
|
|
||||||
HttpServer::new(move || {
|
|
||||||
App::new()
|
|
||||||
.app_data(app_data.clone())
|
|
||||||
.wrap(CookieSession::signed(&private_key).secure(false))
|
|
||||||
.wrap(middleware::Compress::new(ContentEncoding::Gzip))
|
|
||||||
.service(index)
|
|
||||||
.service(user::auth::auth)
|
|
||||||
.service(agent::remote::target_validate)
|
|
||||||
.service(agent::remote::target_remote)
|
|
||||||
.service(agent::ws::ws_index)
|
|
||||||
.service(
|
|
||||||
fs::Files::new("/static", STATIC_DIR)
|
|
||||||
.prefer_utf8(true)
|
|
||||||
.index_file(PAGE_INDEX)
|
|
||||||
.use_etag(true)
|
|
||||||
.default_handler(web::route().to(p404)),
|
|
||||||
)
|
|
||||||
.default_service(web::route().to(p404))
|
|
||||||
})
|
|
||||||
.bind("127.0.0.1:8080")?
|
|
||||||
.run()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix::{Actor, Addr, Context, Handler, Message, MessageResponse};
|
|
||||||
use actix_web::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use log::info;
|
|
||||||
|
|
||||||
#[derive(MessageResponse)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
enum AuthResult {
|
|
||||||
AuthSuccess,
|
|
||||||
AuthFailure,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
#[rtype(result = "AuthResult")]
|
|
||||||
enum AuthMsg {
|
|
||||||
DoAuth(AuthInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct AuthInfo {
|
|
||||||
username: String,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Authenticator;
|
|
||||||
|
|
||||||
impl Authenticator {
|
|
||||||
pub fn new() -> Addr<Self> {
|
|
||||||
Self {}.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for Authenticator {
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, _ctx: &mut Self::Context) {
|
|
||||||
info!("AuthInfo started");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
|
||||||
info!("AuthInfo stopped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<AuthMsg> for Authenticator {
|
|
||||||
type Result = AuthResult;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: AuthMsg, _ctx: &mut Context<Self>) -> Self::Result {
|
|
||||||
match msg {
|
|
||||||
AuthMsg::DoAuth(_auth_info) => {
|
|
||||||
// if auth_info.username == "admin" && auth_info.password == "admin" {
|
|
||||||
// AuthResult::AuthSuccess
|
|
||||||
// } else {
|
|
||||||
// AuthResult::AuthFailure
|
|
||||||
// }
|
|
||||||
AuthResult::AuthSuccess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/auth")]
|
|
||||||
pub async fn auth(params: web::Json<AuthInfo>, req: HttpRequest) -> Result<HttpResponse, Error> {
|
|
||||||
let auth_info = params.into_inner();
|
|
||||||
let app_data = req.app_data::<Arc<crate::AppData>>().unwrap();
|
|
||||||
let res = app_data
|
|
||||||
.authenticator
|
|
||||||
.send(AuthMsg::DoAuth(auth_info))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(AuthResult::AuthSuccess) => Ok(HttpResponse::Ok().json(json!({
|
|
||||||
"status": "success",
|
|
||||||
}))),
|
|
||||||
_ => Ok(HttpResponse::Ok().json(json!({
|
|
||||||
"status": "failure",
|
|
||||||
}))),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
pub mod auth;
|
|
10
frontend/.gitignore
vendored
10
frontend/.gitignore
vendored
@ -1,10 +0,0 @@
|
|||||||
# Generated by Cargo
|
|
||||||
# will have compiled files and executables
|
|
||||||
/target/
|
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
|
||||||
**/*.rs.bk
|
|
@ -1,56 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = [
|
|
||||||
"Jovi Hsu <jv.hsu@outlook.com>"
|
|
||||||
]
|
|
||||||
categories = ["wasm", "web-programming", "sslvpn"]
|
|
||||||
description = ""
|
|
||||||
edition = "2021"
|
|
||||||
keywords = ["yew", "wasm", "wasm-bindgen", "web", "sslvpn"]
|
|
||||||
license = "GPL3"
|
|
||||||
name = "webgateway-fe"
|
|
||||||
readme = "README.md"
|
|
||||||
version = "0.1.0"
|
|
||||||
repository = "https://www.github.com/HsuJv/webgateway"
|
|
||||||
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
wasm-bindgen = "^0.2"
|
|
||||||
yew = "0.18"
|
|
||||||
js-sys = "0.3.55"
|
|
||||||
web-sys = {version="0.3.55", features=["HtmlCanvasElement", "CanvasRenderingContext2d", "ImageData"]}
|
|
||||||
gloo = "0.4.0"
|
|
||||||
yew-router = "0.15"
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
|
||||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
|
||||||
# code size when deploying.
|
|
||||||
console_error_panic_hook = { version = "0.1.6", optional = true }
|
|
||||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
|
||||||
# compared to the default allocator's ~10K. It is slower than the default
|
|
||||||
# allocator, however.
|
|
||||||
wee_alloc = { version = "0.4", optional = true }
|
|
||||||
serde_json = "1.0"
|
|
||||||
anyhow = "1.0"
|
|
||||||
|
|
||||||
magic-crypt= "3"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["console_error_panic_hook", "wee_alloc"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
wasm-bindgen-test = "0.3.13"
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
panic = "unwind"
|
|
||||||
opt-level = 0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
panic = 'abort'
|
|
||||||
codegen-units = 1
|
|
||||||
opt-level = 's'
|
|
||||||
lto = true
|
|
@ -1,29 +0,0 @@
|
|||||||
[tasks.build-debug]
|
|
||||||
command = "wasm-pack"
|
|
||||||
args = ["build", "--target", "web", "--out-name", "wasm", "--out-dir", "./pkg", "--dev"]
|
|
||||||
|
|
||||||
[tasks.build-release]
|
|
||||||
command = "wasm-pack"
|
|
||||||
args = ["build", "--target", "web", "--out-name", "wasm", "--out-dir", "./pkg"]
|
|
||||||
|
|
||||||
[tasks.install-debug]
|
|
||||||
dependencies=["build-debug", "install_wasm", "install_html"]
|
|
||||||
|
|
||||||
[tasks.install-release]
|
|
||||||
dependencies=["build-release", "install_wasm", "install_html"]
|
|
||||||
|
|
||||||
[tasks.install_wasm]
|
|
||||||
script = '''
|
|
||||||
mkdir -p $FE_STATIC_INSTALL_PATH
|
|
||||||
cp ./pkg/wasm.js $FE_STATIC_INSTALL_PATH
|
|
||||||
cp ./pkg/wasm_bg.wasm $FE_STATIC_INSTALL_PATH
|
|
||||||
'''
|
|
||||||
|
|
||||||
[tasks.install_html]
|
|
||||||
script = '''
|
|
||||||
mkdir -p $FE_STATIC_INSTALL_PATH
|
|
||||||
cp static/* $FE_STATIC_INSTALL_PATH
|
|
||||||
'''
|
|
||||||
|
|
||||||
[env]
|
|
||||||
FE_STATIC_INSTALL_PATH="${INSTALL_PATH}/static"
|
|
@ -1,141 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use crate::components::auth;
|
|
||||||
use crate::pages::{page_home::PageHome, page_not_found::PageNotFound};
|
|
||||||
use yew::html::IntoPropValue;
|
|
||||||
use yew::prelude::*;
|
|
||||||
use yew::services::ConsoleService;
|
|
||||||
use yew::Component;
|
|
||||||
use yew_router::prelude::*;
|
|
||||||
use yew_router::{router::Router, Switch};
|
|
||||||
|
|
||||||
#[derive(Switch, Clone, Debug)]
|
|
||||||
enum AppRoute {
|
|
||||||
// #[at("/ssh/:id")]
|
|
||||||
// Ssh(i32),
|
|
||||||
// #[to = "/ssh"]
|
|
||||||
// Ssh,
|
|
||||||
#[to = "/!"]
|
|
||||||
Home,
|
|
||||||
#[to = ""]
|
|
||||||
NotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AppRoute> for &str {
|
|
||||||
fn from(route: AppRoute) -> Self {
|
|
||||||
match route {
|
|
||||||
// AppRoute::Ssh => "/ssh",
|
|
||||||
_ => "/",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoPropValue<Option<Cow<'_, str>>> for AppRoute {
|
|
||||||
fn into_prop_value(self: AppRoute) -> Option<Cow<'static, str>> {
|
|
||||||
Some(Cow::Borrowed(self.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct App {
|
|
||||||
authdone: bool,
|
|
||||||
link: ComponentLink<Self>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum AppMsg {
|
|
||||||
AuthDone,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for App {
|
|
||||||
type Message = AppMsg;
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
authdone: false,
|
|
||||||
link,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
|
||||||
match msg {
|
|
||||||
AppMsg::AuthDone => self.authdone = true,
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
{
|
|
||||||
|
|
||||||
if self.authdone {
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
{self.view_nav()}
|
|
||||||
|
|
||||||
<main class="content">
|
|
||||||
<Router<AppRoute>
|
|
||||||
render = Router::render(Self::switch)
|
|
||||||
redirect=Router::redirect(|route: Route| {
|
|
||||||
ConsoleService::log(&format!("{:?}", route));
|
|
||||||
AppRoute::NotFound
|
|
||||||
})
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let onauthdone = &self.link.callback(|_| AppMsg::AuthDone);
|
|
||||||
html!{
|
|
||||||
<auth::AuthComponents onauthdone=onauthdone/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<footer class="footer">
|
|
||||||
// { "Powered by " }
|
|
||||||
// <a href="https://yew.rs">{ "Yew" }</a>
|
|
||||||
</footer>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
fn view_nav(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class=classes!("navbar-menu")>
|
|
||||||
<RouterAnchor<AppRoute> classes="navbar-item" route=AppRoute::Home>
|
|
||||||
{ "Home" }
|
|
||||||
</RouterAnchor<AppRoute>>
|
|
||||||
// <RouterAnchor<AppRoute> classes="navbar-item" route=AppRoute::Ssh>
|
|
||||||
// { "Ssh" }
|
|
||||||
// </RouterAnchor<AppRoute>>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn switch(switch: AppRoute) -> Html {
|
|
||||||
ConsoleService::log(&format!("{:?}", switch));
|
|
||||||
match switch {
|
|
||||||
// Route::Ssh(ip) => {
|
|
||||||
// html! { <Ssh /> }
|
|
||||||
// }
|
|
||||||
// AppRoute::Ssh => {
|
|
||||||
// html! {<PageSsh />}
|
|
||||||
// }
|
|
||||||
AppRoute::Home => {
|
|
||||||
html! {<PageHome />}
|
|
||||||
}
|
|
||||||
AppRoute::NotFound => {
|
|
||||||
html! { <PageNotFound /> }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,154 +0,0 @@
|
|||||||
use super::input::Input;
|
|
||||||
use anyhow;
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use yew::services::{
|
|
||||||
fetch::{FetchTask, Request},
|
|
||||||
FetchService,
|
|
||||||
};
|
|
||||||
use yew::{format::Json, services::fetch::Response};
|
|
||||||
use yew::{prelude::*, services::ConsoleService};
|
|
||||||
pub enum AuthMsg {
|
|
||||||
UpdateUsername(String),
|
|
||||||
UpdatePassword(String),
|
|
||||||
AuthRequest,
|
|
||||||
AuthResponse(Result<Value, anyhow::Error>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AuthComponents {
|
|
||||||
username: String,
|
|
||||||
password: String,
|
|
||||||
link: ComponentLink<Self>,
|
|
||||||
auth_result: String,
|
|
||||||
fetch_task: Option<FetchTask>,
|
|
||||||
onauthdone: Callback<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for AuthComponents {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"AuthComponents {{ username: {}, password: {} }}",
|
|
||||||
self.username, self.password
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
|
||||||
pub struct AuthProps {
|
|
||||||
#[prop_or_default]
|
|
||||||
pub onauthdone: Callback<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for AuthComponents {
|
|
||||||
type Message = AuthMsg;
|
|
||||||
type Properties = AuthProps;
|
|
||||||
|
|
||||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
|
||||||
AuthComponents {
|
|
||||||
username: String::new(),
|
|
||||||
password: String::new(),
|
|
||||||
auth_result: String::new(),
|
|
||||||
link,
|
|
||||||
fetch_task: None,
|
|
||||||
onauthdone: props.onauthdone,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
|
||||||
match msg {
|
|
||||||
AuthMsg::UpdateUsername(username) => {
|
|
||||||
self.username = username;
|
|
||||||
self.auth_result.clear();
|
|
||||||
}
|
|
||||||
AuthMsg::UpdatePassword(password) => {
|
|
||||||
self.password = password;
|
|
||||||
self.auth_result.clear();
|
|
||||||
}
|
|
||||||
AuthMsg::AuthRequest => {
|
|
||||||
let auth_info = json!({
|
|
||||||
"username": self.username,
|
|
||||||
"password": self.password,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1. build the request
|
|
||||||
let request = Request::post("/auth")
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.body(Json(&auth_info))
|
|
||||||
.expect("Could not build auth request.");
|
|
||||||
// 2. construct a callback
|
|
||||||
let callback =
|
|
||||||
self.link
|
|
||||||
.callback(|response: Response<Json<Result<Value, anyhow::Error>>>| {
|
|
||||||
// ConsoleService::error(&format!("{:?}", response));
|
|
||||||
let Json(data) = response.into_body();
|
|
||||||
AuthMsg::AuthResponse(data)
|
|
||||||
});
|
|
||||||
// 3. pass the request and callback to the fetch service
|
|
||||||
let task = FetchService::fetch(request, callback).expect("failed to start request");
|
|
||||||
// 4. store the task so it isn't canceled immediately
|
|
||||||
self.fetch_task = Some(task);
|
|
||||||
}
|
|
||||||
AuthMsg::AuthResponse(response) => {
|
|
||||||
if let Ok(response) = response {
|
|
||||||
self.auth_result = response["status"].to_string();
|
|
||||||
if "\"success\"" == self.auth_result {
|
|
||||||
self.onauthdone.emit(());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.auth_result = String::from("Auth failed with unknown reason");
|
|
||||||
ConsoleService::error(&format!("{:?}", response.unwrap_err().to_string()));
|
|
||||||
}
|
|
||||||
// release resources
|
|
||||||
self.fetch_task = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ConsoleService::log(&format!(
|
|
||||||
// "username: {}, password {}",
|
|
||||||
// self.username, self.password
|
|
||||||
// ));
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
let link = &self.link;
|
|
||||||
|
|
||||||
let update_uname = link.callback(AuthMsg::UpdateUsername);
|
|
||||||
|
|
||||||
let update_pword = link.callback(AuthMsg::UpdatePassword);
|
|
||||||
|
|
||||||
let auth_post = link.callback(|_| AuthMsg::AuthRequest);
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<div class="horizontal-centre vertical-centre">
|
|
||||||
<label for="username">{"Username: "}</label>
|
|
||||||
<Input id="username" type_="text" placeholder="Username" on_change={update_uname} />
|
|
||||||
<br />
|
|
||||||
<label for="password">{"Password: "}</label>
|
|
||||||
<Input id="password" type_="password" placeholder="Password" on_change={update_pword} />
|
|
||||||
<br />
|
|
||||||
<button type="submit" onclick={auth_post}>{"Login"}</button>
|
|
||||||
<br />
|
|
||||||
{self.auth_result_view()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuthComponents {
|
|
||||||
fn auth_result_view(&self) -> Html {
|
|
||||||
if self.fetch_task.is_some() {
|
|
||||||
html! {
|
|
||||||
<div>{"Authing..."}</div>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html! {
|
|
||||||
<div>{self.auth_result.clone()}</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
use crate::utils::WeakComponentLink;
|
|
||||||
|
|
||||||
pub enum ClipboardMsg {
|
|
||||||
UpdateClipboard(String),
|
|
||||||
SendClipboard,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Clipboard {
|
|
||||||
link: ComponentLink<Self>,
|
|
||||||
onsubmit: Callback<String>,
|
|
||||||
text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Props
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
|
||||||
pub struct ClipboardProps {
|
|
||||||
#[prop_or_default]
|
|
||||||
pub weak_link: WeakComponentLink<Clipboard>,
|
|
||||||
pub onsubmit: Callback<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Clipboard {
|
|
||||||
type Message = ClipboardMsg;
|
|
||||||
type Properties = ClipboardProps;
|
|
||||||
|
|
||||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
|
||||||
props.weak_link.borrow_mut().replace(link.clone());
|
|
||||||
Clipboard {
|
|
||||||
link,
|
|
||||||
onsubmit: props.onsubmit,
|
|
||||||
text: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
|
||||||
match msg {
|
|
||||||
ClipboardMsg::UpdateClipboard(text) => {
|
|
||||||
self.text = text;
|
|
||||||
}
|
|
||||||
ClipboardMsg::SendClipboard => {
|
|
||||||
self.onsubmit.emit(self.text.clone());
|
|
||||||
self.text.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
let update_clipboard = self.link.callback(|e: ChangeData| match e {
|
|
||||||
ChangeData::Value(v) => ClipboardMsg::UpdateClipboard(v),
|
|
||||||
_ => panic!("unexpected message"),
|
|
||||||
});
|
|
||||||
let set_clipboard = self.link.callback(|_| ClipboardMsg::SendClipboard);
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<textarea rows="5" cols="60" id="clipboard" onchange=update_clipboard value=self.text.clone()/>
|
|
||||||
<br/>
|
|
||||||
<button id="clipboard-send" onclick=set_clipboard> {"Send to peer"} </button>
|
|
||||||
<br/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
use serde_json::{json, Value};
|
|
||||||
use yew::{
|
|
||||||
format::Json,
|
|
||||||
prelude::*,
|
|
||||||
services::{
|
|
||||||
fetch::{FetchTask, Request, Response},
|
|
||||||
ConsoleService, FetchService,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub enum HostMsg {
|
|
||||||
UpdateHost(String),
|
|
||||||
UpdatePort(String),
|
|
||||||
ValidateResponse(Result<Value, anyhow::Error>),
|
|
||||||
ConnectHost,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Host {
|
|
||||||
link: ComponentLink<Self>,
|
|
||||||
host: String,
|
|
||||||
port: u16,
|
|
||||||
error_msg: String,
|
|
||||||
onsubmit: Callback<(String, u16)>,
|
|
||||||
fetch_task: Option<FetchTask>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Props
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
|
||||||
pub struct HostProps {
|
|
||||||
#[prop_or_default]
|
|
||||||
pub onsubmit: Callback<(String, u16)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Host {
|
|
||||||
type Message = HostMsg;
|
|
||||||
type Properties = HostProps;
|
|
||||||
|
|
||||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
|
||||||
Host {
|
|
||||||
link,
|
|
||||||
host: "".to_string(),
|
|
||||||
port: 0,
|
|
||||||
error_msg: "".to_string(),
|
|
||||||
onsubmit: props.onsubmit,
|
|
||||||
fetch_task: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
|
||||||
match msg {
|
|
||||||
HostMsg::UpdateHost(host) => {
|
|
||||||
self.host = host;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
HostMsg::UpdatePort(port) => match port.parse::<u16>() {
|
|
||||||
Ok(port) => {
|
|
||||||
self.port = port;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
self.error_msg = "Port must be a number".to_string();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
HostMsg::ValidateResponse(response) => {
|
|
||||||
if let Ok(response) = response {
|
|
||||||
self.error_msg = response["status"].to_string();
|
|
||||||
|
|
||||||
if "\"success\"" == self.error_msg {
|
|
||||||
let mut ip = response["ip"].to_string();
|
|
||||||
let _ = ip.pop();
|
|
||||||
let _ = ip.remove(0);
|
|
||||||
self.onsubmit.emit((ip, self.port));
|
|
||||||
} else {
|
|
||||||
self.error_msg = response["message"].to_string();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.error_msg = String::from("Valid host failed with unknown reason");
|
|
||||||
ConsoleService::error(&format!("{:?}", response.unwrap_err().to_string()));
|
|
||||||
}
|
|
||||||
// release resources
|
|
||||||
self.fetch_task = None;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
HostMsg::ConnectHost => {
|
|
||||||
let to_post = json!({
|
|
||||||
"host": self.host,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1. build the request
|
|
||||||
let request = Request::post("/target/validate")
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.body(Json(&to_post))
|
|
||||||
.expect("Could not build auth request.");
|
|
||||||
// 2. construct a callback
|
|
||||||
let callback =
|
|
||||||
self.link
|
|
||||||
.callback(|response: Response<Json<Result<Value, anyhow::Error>>>| {
|
|
||||||
// ConsoleService::error(&format!("{:?}", response));
|
|
||||||
let Json(data) = response.into_body();
|
|
||||||
HostMsg::ValidateResponse(data)
|
|
||||||
});
|
|
||||||
// 3. pass the request and callback to the fetch service
|
|
||||||
let task = FetchService::fetch(request, callback).expect("failed to start request");
|
|
||||||
// 4. store the task so it isn't canceled immediately
|
|
||||||
self.fetch_task = Some(task);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
let updatehost = self.link.callback(|e: ChangeData| match e {
|
|
||||||
ChangeData::Value(val) => HostMsg::UpdateHost(val),
|
|
||||||
_ => panic!("unexpected message"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let updateport = self.link.callback(|e: ChangeData| match e {
|
|
||||||
ChangeData::Value(val) => HostMsg::UpdatePort(val),
|
|
||||||
_ => panic!("unexpected message"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let connecthost = self.link.callback(|_| HostMsg::ConnectHost);
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<div class="horizontal-centre vertical-centre">
|
|
||||||
<label for="hostname">{"Hostname: "}</label>
|
|
||||||
<input id="hostname" type="text" placeholder="hostname" onchange={updatehost} />
|
|
||||||
<br />
|
|
||||||
<label for="port">{" Port: "}</label>
|
|
||||||
<input id="port" type="text" placeholder="port" onchange={updateport}/>
|
|
||||||
<br />
|
|
||||||
<button onclick={connecthost}>{"Connect"}</button>
|
|
||||||
<br />
|
|
||||||
{self.error_msg.clone()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
// message on update
|
|
||||||
pub enum InputMsg {
|
|
||||||
Update(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
// props on_change
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
|
||||||
pub struct InputProps {
|
|
||||||
pub on_change: Callback<String>,
|
|
||||||
pub id: String,
|
|
||||||
pub type_: String,
|
|
||||||
pub placeholder: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// component input
|
|
||||||
pub struct Input {
|
|
||||||
link: ComponentLink<Self>,
|
|
||||||
on_change: Callback<String>,
|
|
||||||
id: String,
|
|
||||||
type_: String,
|
|
||||||
placeholder: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Input {
|
|
||||||
type Message = InputMsg;
|
|
||||||
type Properties = InputProps;
|
|
||||||
|
|
||||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
|
||||||
Input {
|
|
||||||
link,
|
|
||||||
on_change: props.on_change,
|
|
||||||
id: props.id,
|
|
||||||
type_: props.type_,
|
|
||||||
placeholder: props.placeholder,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
|
||||||
match msg {
|
|
||||||
InputMsg::Update(text) => {
|
|
||||||
self.on_change.emit(text);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
let on_change = self.link.callback(|e: ChangeData| match e {
|
|
||||||
ChangeData::Value(v) => InputMsg::Update(v),
|
|
||||||
_ => panic!("unexpected message"),
|
|
||||||
});
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<input
|
|
||||||
id={self.id.clone()}
|
|
||||||
type={self.type_.clone()}
|
|
||||||
placeholder={self.placeholder.clone()}
|
|
||||||
onchange={on_change}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
pub mod auth;
|
|
||||||
pub mod clipboard;
|
|
||||||
pub mod host;
|
|
||||||
pub mod input;
|
|
||||||
pub mod ws;
|
|
@ -1,111 +0,0 @@
|
|||||||
use yew::prelude::*;
|
|
||||||
use yew::services::websocket::{WebSocketService, WebSocketStatus, WebSocketTask};
|
|
||||||
use yew::services::ConsoleService;
|
|
||||||
use yew::{format::Binary, utils::host};
|
|
||||||
|
|
||||||
use crate::utils::WeakComponentLink;
|
|
||||||
|
|
||||||
pub struct WebsocketCtx {
|
|
||||||
ws: Option<WebSocketTask>,
|
|
||||||
link: ComponentLink<Self>,
|
|
||||||
error_msg: String,
|
|
||||||
onrecv: Callback<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
|
||||||
pub struct WebsocketProps {
|
|
||||||
#[prop_or_default]
|
|
||||||
pub onrecv: Callback<Vec<u8>>,
|
|
||||||
pub weak_link: WeakComponentLink<WebsocketCtx>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum WebsocketMsg {
|
|
||||||
Connect,
|
|
||||||
Disconnected,
|
|
||||||
Ignore,
|
|
||||||
Send(Binary),
|
|
||||||
Recv(Binary),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for WebsocketCtx {
|
|
||||||
type Message = WebsocketMsg;
|
|
||||||
type Properties = WebsocketProps;
|
|
||||||
|
|
||||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
|
||||||
props.weak_link.borrow_mut().replace(link.clone());
|
|
||||||
Self {
|
|
||||||
ws: None,
|
|
||||||
link,
|
|
||||||
error_msg: String::new(),
|
|
||||||
onrecv: props.onrecv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
|
||||||
match msg {
|
|
||||||
WebsocketMsg::Connect => {
|
|
||||||
ConsoleService::log("Connecting");
|
|
||||||
let cbout = self.link.callback(WebsocketMsg::Recv);
|
|
||||||
let cbnot = self.link.callback(|input| {
|
|
||||||
ConsoleService::log(&format!("Notification: {:?}", input));
|
|
||||||
match input {
|
|
||||||
WebSocketStatus::Closed | WebSocketStatus::Error => {
|
|
||||||
WebsocketMsg::Disconnected
|
|
||||||
}
|
|
||||||
_ => WebsocketMsg::Ignore,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if self.ws.is_none() {
|
|
||||||
let task = WebSocketService::connect_binary(
|
|
||||||
&format!("ws://{}/ws", host().unwrap()),
|
|
||||||
cbout,
|
|
||||||
cbnot,
|
|
||||||
);
|
|
||||||
self.ws = Some(task.unwrap());
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
WebsocketMsg::Disconnected => {
|
|
||||||
self.ws = None;
|
|
||||||
self.error_msg = "Disconnected".to_string();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
WebsocketMsg::Ignore => false,
|
|
||||||
WebsocketMsg::Send(data) => {
|
|
||||||
if let Some(ref mut ws) = self.ws {
|
|
||||||
ws.send_binary(data);
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
WebsocketMsg::Recv(Ok(s)) => {
|
|
||||||
// ConsoleService::log(&format!("recv {:?}", s));
|
|
||||||
self.onrecv.emit(s);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
WebsocketMsg::Recv(Err(s)) => {
|
|
||||||
self.error_msg = format!("Error when reading from server: {}\n", &s.to_string());
|
|
||||||
self.link.send_message(WebsocketMsg::Disconnected);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _prop: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
{self.error_msg.clone()}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rendered(&mut self, first_render: bool) {
|
|
||||||
if first_render && self.ws.is_none() {
|
|
||||||
ConsoleService::log(&"Start websocket".to_string());
|
|
||||||
self.link.send_message(WebsocketMsg::Connect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
mod app;
|
|
||||||
mod components;
|
|
||||||
mod pages;
|
|
||||||
mod protocal;
|
|
||||||
mod utils;
|
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "wee_alloc")]
|
|
||||||
#[global_allocator]
|
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn run_app() -> Result<(), JsValue> {
|
|
||||||
yew::start_app::<app::App>();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
pub mod page_home;
|
|
||||||
pub mod page_not_found;
|
|
||||||
pub mod page_vnc;
|
|
@ -1,28 +0,0 @@
|
|||||||
use yew::prelude::*;
|
|
||||||
use yew::ShouldRender;
|
|
||||||
use yew::{html, Component, Html};
|
|
||||||
|
|
||||||
pub struct PageHome;
|
|
||||||
|
|
||||||
impl Component for PageHome {
|
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<crate::pages::page_vnc::PageVnc/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
use yew::prelude::*;
|
|
||||||
use yew::Component;
|
|
||||||
use yew::ShouldRender;
|
|
||||||
|
|
||||||
pub struct PageNotFound;
|
|
||||||
|
|
||||||
impl Component for PageNotFound {
|
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<section class="hero is-danger is-bold is-large">
|
|
||||||
<div class="hero-body">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="title">
|
|
||||||
{ "Page not found" }
|
|
||||||
</h1>
|
|
||||||
<h2 class="subtitle">
|
|
||||||
{ "Page page does not seem to exist" }
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,473 +0,0 @@
|
|||||||
use serde_json::{json, Value};
|
|
||||||
use wasm_bindgen::{prelude::Closure, Clamped, JsCast, JsValue};
|
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData};
|
|
||||||
use yew::{
|
|
||||||
format::Json,
|
|
||||||
html,
|
|
||||||
prelude::*,
|
|
||||||
services::{
|
|
||||||
fetch::{FetchTask, Request, Response},
|
|
||||||
ConsoleService, FetchService,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use gloo::timers::callback::Interval;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
components::{self, input::Input, ws::WebsocketMsg},
|
|
||||||
protocal::{common::*, vnc::vnc::VncHandler},
|
|
||||||
utils::WeakComponentLink,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct PageVnc {
|
|
||||||
link: ComponentLink<Self>,
|
|
||||||
target: (String, u16),
|
|
||||||
error_msg: String,
|
|
||||||
fetch_task: Option<FetchTask>,
|
|
||||||
connected: bool,
|
|
||||||
handler: ProtocalHandler<VncHandler>,
|
|
||||||
websocket: WeakComponentLink<components::ws::WebsocketCtx>,
|
|
||||||
request_username: bool,
|
|
||||||
request_password: bool,
|
|
||||||
username: String,
|
|
||||||
password: String,
|
|
||||||
canvas: NodeRef,
|
|
||||||
canvas_ctx: Option<CanvasRenderingContext2d>,
|
|
||||||
interval: Option<Interval>,
|
|
||||||
clipboard: WeakComponentLink<components::clipboard::Clipboard>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
|
||||||
pub struct VncProps {}
|
|
||||||
|
|
||||||
pub enum VncMsg {
|
|
||||||
Connect((String, u16)),
|
|
||||||
ConnectResp(Result<Value, anyhow::Error>),
|
|
||||||
Connected,
|
|
||||||
Recv(Vec<u8>),
|
|
||||||
Send(Vec<u8>),
|
|
||||||
UpdateUsername(String),
|
|
||||||
UpdatePassword(String),
|
|
||||||
UpdateClipboard(String),
|
|
||||||
SendCredential,
|
|
||||||
RequireFrame(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for PageVnc {
|
|
||||||
type Message = VncMsg;
|
|
||||||
type Properties = VncProps;
|
|
||||||
|
|
||||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
|
||||||
PageVnc {
|
|
||||||
link,
|
|
||||||
target: (String::from(""), 0),
|
|
||||||
error_msg: String::from(""),
|
|
||||||
fetch_task: None,
|
|
||||||
connected: false,
|
|
||||||
handler: ProtocalHandler::new(),
|
|
||||||
websocket: WeakComponentLink::default(),
|
|
||||||
request_username: false,
|
|
||||||
request_password: false,
|
|
||||||
username: String::from(""),
|
|
||||||
password: String::from(""),
|
|
||||||
canvas: NodeRef::default(),
|
|
||||||
canvas_ctx: None,
|
|
||||||
interval: None,
|
|
||||||
clipboard: WeakComponentLink::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
|
||||||
match msg {
|
|
||||||
VncMsg::Connect(target) => {
|
|
||||||
self.target = target;
|
|
||||||
// ConsoleService::log(&self.target);
|
|
||||||
let to_post = json!({
|
|
||||||
"ip": self.target.0,
|
|
||||||
"port": self.target.1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1. build the request
|
|
||||||
let request = Request::post("/target/remote")
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.body(Json(&to_post))
|
|
||||||
.expect("Could not build auth request.");
|
|
||||||
// 2. construct a callback
|
|
||||||
let callback =
|
|
||||||
self.link
|
|
||||||
.callback(|response: Response<Json<Result<Value, anyhow::Error>>>| {
|
|
||||||
// ConsoleService::error(&format!("{:?}", response));
|
|
||||||
let Json(data) = response.into_body();
|
|
||||||
VncMsg::ConnectResp(data)
|
|
||||||
});
|
|
||||||
// 3. pass the request and callback to the fetch service
|
|
||||||
let task = FetchService::fetch(request, callback).expect("failed to start request");
|
|
||||||
// 4. store the task so it isn't canceled immediately
|
|
||||||
self.fetch_task = Some(task);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
VncMsg::ConnectResp(response) => {
|
|
||||||
if let Ok(response) = response {
|
|
||||||
self.error_msg = response["status"].to_string();
|
|
||||||
|
|
||||||
if "\"success\"" == self.error_msg {
|
|
||||||
self.link.send_message(VncMsg::Connected);
|
|
||||||
} else {
|
|
||||||
self.error_msg = response["message"].to_string();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.error_msg = String::from("Connect host failed with unknown reason");
|
|
||||||
ConsoleService::error(&format!("{:?}", response.unwrap_err().to_string()));
|
|
||||||
}
|
|
||||||
// release resources
|
|
||||||
self.fetch_task = None;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
VncMsg::Connected => {
|
|
||||||
self.connected = true;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
VncMsg::Recv(v) => {
|
|
||||||
self.handler.do_input(v);
|
|
||||||
self.protocal_out_handler()
|
|
||||||
}
|
|
||||||
VncMsg::Send(v) => {
|
|
||||||
self.websocket
|
|
||||||
.borrow()
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.send_message(WebsocketMsg::Send(Ok(v)));
|
|
||||||
false
|
|
||||||
}
|
|
||||||
VncMsg::UpdateUsername(username) => {
|
|
||||||
self.username = username;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
VncMsg::UpdatePassword(password) => {
|
|
||||||
self.password = password;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
VncMsg::SendCredential => {
|
|
||||||
self.request_username = false;
|
|
||||||
self.request_password = false;
|
|
||||||
self.handler.set_credential(&self.username, &self.password);
|
|
||||||
self.protocal_out_handler()
|
|
||||||
}
|
|
||||||
VncMsg::RequireFrame(incremental) => {
|
|
||||||
self.handler.require_frame(incremental);
|
|
||||||
if self.interval.is_none() {
|
|
||||||
let link = self.link.clone();
|
|
||||||
let tick =
|
|
||||||
Interval::new(20, move || link.send_message(VncMsg::RequireFrame(1)));
|
|
||||||
self.interval = Some(tick);
|
|
||||||
}
|
|
||||||
self.protocal_out_handler()
|
|
||||||
}
|
|
||||||
VncMsg::UpdateClipboard(clipboard) => {
|
|
||||||
if clipboard.len() > 0 {
|
|
||||||
self.handler.set_clipboard(&clipboard);
|
|
||||||
self.protocal_out_handler()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
if !self.connected {
|
|
||||||
let connect_remote = self.link.callback(VncMsg::Connect);
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<components::host::Host onsubmit=connect_remote/>
|
|
||||||
{self.error_msg.clone()}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let recv_msg = self.link.callback(VncMsg::Recv);
|
|
||||||
let clipboard_update = self.link.callback(VncMsg::UpdateClipboard);
|
|
||||||
let websocket = &self.websocket;
|
|
||||||
let clipboard = &self.clipboard;
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<div class="horizontal-centre vertical-centre">
|
|
||||||
{self.username_view()}
|
|
||||||
{self.password_view()}
|
|
||||||
{self.button_connect_view()}
|
|
||||||
<components::ws::WebsocketCtx
|
|
||||||
weak_link=websocket onrecv=recv_msg/>
|
|
||||||
<canvas id="remote-canvas" ref=self.canvas.clone()
|
|
||||||
tabIndex=1></canvas>
|
|
||||||
<components::clipboard::Clipboard
|
|
||||||
weak_link=clipboard onsubmit=clipboard_update/>
|
|
||||||
{self.error_msg.clone()}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rendered(&mut self, first_render: bool) {
|
|
||||||
if first_render {
|
|
||||||
self.handler.set_resolution(1366, 768);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl PageRemote
|
|
||||||
impl PageVnc {
|
|
||||||
fn protocal_out_handler(&mut self) -> ShouldRender {
|
|
||||||
let out = self.handler.get_output();
|
|
||||||
let mut should_render = false;
|
|
||||||
if !out.is_empty() {
|
|
||||||
for o in out {
|
|
||||||
match o {
|
|
||||||
ProtocalHandlerOutput::Err(err) => {
|
|
||||||
self.error_msg = err.clone();
|
|
||||||
self.websocket
|
|
||||||
.borrow_mut()
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.send_message(WebsocketMsg::Disconnected);
|
|
||||||
should_render = true;
|
|
||||||
}
|
|
||||||
ProtocalHandlerOutput::WsBuf(out) => {
|
|
||||||
if out.len() > 0 {
|
|
||||||
self.link.send_message(VncMsg::Send(out));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ProtocalHandlerOutput::RequirePassword => {
|
|
||||||
self.request_password = true;
|
|
||||||
should_render = true;
|
|
||||||
}
|
|
||||||
ProtocalHandlerOutput::RenderCanvas(cr) => {
|
|
||||||
let canvas = self.canvas.cast::<HtmlCanvasElement>().unwrap();
|
|
||||||
let ctx = match &self.canvas_ctx {
|
|
||||||
Some(ctx) => ctx,
|
|
||||||
None => {
|
|
||||||
let ctx = CanvasRenderingContext2d::from(JsValue::from(
|
|
||||||
canvas.get_context("2d").unwrap().unwrap(),
|
|
||||||
));
|
|
||||||
self.canvas_ctx = Some(ctx);
|
|
||||||
self.canvas_ctx.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match cr.type_ {
|
|
||||||
1 => {
|
|
||||||
//copy
|
|
||||||
let sx = (cr.data[0] as u16) << 8 | cr.data[1] as u16;
|
|
||||||
let sy = (cr.data[2] as u16) << 8 | cr.data[3] as u16;
|
|
||||||
|
|
||||||
let _ = ctx.
|
|
||||||
draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
|
|
||||||
&canvas,
|
|
||||||
sx as f64,
|
|
||||||
sy as f64,
|
|
||||||
cr.width as f64,
|
|
||||||
cr.height as f64,
|
|
||||||
cr.x as f64,
|
|
||||||
cr.y as f64,
|
|
||||||
cr.width as f64,
|
|
||||||
cr.height as f64
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let data = ImageData::new_with_u8_clamped_array_and_sh(
|
|
||||||
Clamped(&cr.data),
|
|
||||||
cr.width as u32,
|
|
||||||
cr.height as u32,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
// ConsoleService::log(&format!(
|
|
||||||
// "renderring at ({}, {}), width {}, height {}",
|
|
||||||
// cr.x, cr.y, cr.width, cr.height
|
|
||||||
// ));
|
|
||||||
let _ = ctx.put_image_data(&data, cr.x as f64, cr.y as f64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
should_render = true;
|
|
||||||
}
|
|
||||||
ProtocalHandlerOutput::SetCanvas(width, height) => {
|
|
||||||
let canvas = self.canvas.cast::<HtmlCanvasElement>().unwrap();
|
|
||||||
canvas.set_width(width as u32);
|
|
||||||
canvas.set_height(height as u32);
|
|
||||||
self.bind_mouse_and_key(&canvas);
|
|
||||||
self.link.send_message(VncMsg::RequireFrame(0));
|
|
||||||
let ctx = match &self.canvas_ctx {
|
|
||||||
Some(ctx) => ctx,
|
|
||||||
None => {
|
|
||||||
let ctx = CanvasRenderingContext2d::from(JsValue::from(
|
|
||||||
canvas.get_context("2d").unwrap().unwrap(),
|
|
||||||
));
|
|
||||||
self.canvas_ctx = Some(ctx);
|
|
||||||
self.canvas_ctx.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ctx.rect(0 as f64, 0 as f64, width as f64, height as f64);
|
|
||||||
ctx.fill();
|
|
||||||
should_render = true;
|
|
||||||
}
|
|
||||||
ProtocalHandlerOutput::SetClipboard(text) => {
|
|
||||||
self.clipboard.borrow_mut().as_mut().unwrap().send_message(
|
|
||||||
components::clipboard::ClipboardMsg::UpdateClipboard(text),
|
|
||||||
);
|
|
||||||
// ConsoleService::log(&self.error_msg);
|
|
||||||
should_render = false;
|
|
||||||
}
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
should_render
|
|
||||||
}
|
|
||||||
|
|
||||||
fn username_view(&self) -> Html {
|
|
||||||
if self.request_username {
|
|
||||||
let update_username = self.link.callback(VncMsg::UpdateUsername);
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<Input id="username" type_="text" placeholder="username" on_change={update_username}/>
|
|
||||||
<br/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html! {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn password_view(&self) -> Html {
|
|
||||||
if self.request_password {
|
|
||||||
let update_password = self.link.callback(VncMsg::UpdatePassword);
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<Input id="password" type_="password" placeholder="password" on_change={update_password}/>
|
|
||||||
<br/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html! {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn button_connect_view(&self) -> Html {
|
|
||||||
if self.request_username || self.request_password {
|
|
||||||
let send_credential = self.link.callback(|_| VncMsg::SendCredential);
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<button type="submit" onclick={send_credential}>{"Connect"}</button>
|
|
||||||
<br/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html! {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bind_mouse_and_key(&mut self, canvas: &HtmlCanvasElement) {
|
|
||||||
let _window = web_sys::window().unwrap();
|
|
||||||
let handler = self.handler.clone();
|
|
||||||
let key_down = move |e: KeyboardEvent| {
|
|
||||||
e.prevent_default();
|
|
||||||
e.stop_propagation();
|
|
||||||
handler.key_press(e, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
let handler = Box::new(key_down) as Box<dyn FnMut(_)>;
|
|
||||||
|
|
||||||
let cb = Closure::wrap(handler);
|
|
||||||
|
|
||||||
canvas
|
|
||||||
.add_event_listener_with_callback("keydown", cb.as_ref().unchecked_ref())
|
|
||||||
.unwrap();
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
let handler = self.handler.clone();
|
|
||||||
let key_up = move |e: KeyboardEvent| {
|
|
||||||
e.prevent_default();
|
|
||||||
e.stop_propagation();
|
|
||||||
handler.key_press(e, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
let handler = Box::new(key_up) as Box<dyn FnMut(_)>;
|
|
||||||
|
|
||||||
let cb = Closure::wrap(handler);
|
|
||||||
|
|
||||||
canvas
|
|
||||||
.add_event_listener_with_callback("keyup", cb.as_ref().unchecked_ref())
|
|
||||||
.unwrap();
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
// On a conventional mouse, buttons 1, 2, and 3 correspond to the left,
|
|
||||||
// middle, and right buttons on the mouse. On a wheel mouse, each step
|
|
||||||
// of the wheel upwards is represented by a press and release of button
|
|
||||||
// 4, and each step downwards is represented by a press and release of
|
|
||||||
// button 5.
|
|
||||||
|
|
||||||
// to do:
|
|
||||||
// calculate relation position
|
|
||||||
let handler = self.handler.clone();
|
|
||||||
let mouse_move = move |e: MouseEvent| {
|
|
||||||
e.stop_propagation();
|
|
||||||
handler.mouse_event(e, MouseEventType::MouseMove);
|
|
||||||
};
|
|
||||||
|
|
||||||
let handler = Box::new(mouse_move) as Box<dyn FnMut(_)>;
|
|
||||||
|
|
||||||
let cb = Closure::wrap(handler);
|
|
||||||
|
|
||||||
canvas
|
|
||||||
.add_event_listener_with_callback("mousemove", cb.as_ref().unchecked_ref())
|
|
||||||
.unwrap();
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
let handler = self.handler.clone();
|
|
||||||
let mouse_down = move |e: MouseEvent| {
|
|
||||||
e.stop_propagation();
|
|
||||||
handler.mouse_event(e, MouseEventType::MouseDown);
|
|
||||||
};
|
|
||||||
|
|
||||||
let handler = Box::new(mouse_down) as Box<dyn FnMut(_)>;
|
|
||||||
|
|
||||||
let cb = Closure::wrap(handler);
|
|
||||||
|
|
||||||
canvas
|
|
||||||
.add_event_listener_with_callback("mousedown", cb.as_ref().unchecked_ref())
|
|
||||||
.unwrap();
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
let handler = self.handler.clone();
|
|
||||||
let mouse_up = move |e: MouseEvent| {
|
|
||||||
e.stop_propagation();
|
|
||||||
handler.mouse_event(e, MouseEventType::MouseUp);
|
|
||||||
};
|
|
||||||
|
|
||||||
let handler = Box::new(mouse_up) as Box<dyn FnMut(_)>;
|
|
||||||
|
|
||||||
let cb = Closure::wrap(handler);
|
|
||||||
|
|
||||||
canvas
|
|
||||||
.add_event_listener_with_callback("mouseup", cb.as_ref().unchecked_ref())
|
|
||||||
.unwrap();
|
|
||||||
cb.forget();
|
|
||||||
|
|
||||||
let get_context_menu = move |e: MouseEvent| {
|
|
||||||
e.prevent_default();
|
|
||||||
e.stop_propagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
let handler = Box::new(get_context_menu) as Box<dyn FnMut(_)>;
|
|
||||||
|
|
||||||
let cb = Closure::wrap(handler);
|
|
||||||
|
|
||||||
canvas
|
|
||||||
.add_event_listener_with_callback("contextmenu", cb.as_ref().unchecked_ref())
|
|
||||||
.unwrap();
|
|
||||||
cb.forget();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod common;
|
|
||||||
pub mod vnc;
|
|
@ -1,4 +0,0 @@
|
|||||||
mod des;
|
|
||||||
pub mod vnc;
|
|
||||||
mod x11cursor;
|
|
||||||
mod x11keyboard;
|
|
@ -1,49 +0,0 @@
|
|||||||
use std::{cell::RefCell, ops::Deref, rc::Rc};
|
|
||||||
use yew::{
|
|
||||||
html::{ComponentLink, ImplicitClone},
|
|
||||||
Component,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn set_panic_hook() {
|
|
||||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
|
||||||
// `set_panic_hook` function at least once during initialization, and then
|
|
||||||
// we will get better error messages if our code ever panics.
|
|
||||||
//
|
|
||||||
// For more details see
|
|
||||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
|
||||||
#[cfg(feature = "console_error_panic_hook")]
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComponentLink for sending message
|
|
||||||
// Option to be default none, later assign a vaule
|
|
||||||
// RefCell for container, mutable reference
|
|
||||||
// Rc for multiple ownership
|
|
||||||
pub struct WeakComponentLink<COMP: Component>(Rc<RefCell<Option<ComponentLink<COMP>>>>);
|
|
||||||
impl<COMP: Component> Clone for WeakComponentLink<COMP> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self(Rc::clone(&self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<COMP: Component> ImplicitClone for WeakComponentLink<COMP> {}
|
|
||||||
|
|
||||||
impl<COMP: Component> Default for WeakComponentLink<COMP> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(Rc::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<COMP: Component> Deref for WeakComponentLink<COMP> {
|
|
||||||
type Target = Rc<RefCell<Option<ComponentLink<COMP>>>>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<COMP: Component> PartialEq for WeakComponentLink<COMP> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
Rc::ptr_eq(&self.0, &other.0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Web Gateway</title>
|
|
||||||
<style type="text/css">
|
|
||||||
.navbar {
|
|
||||||
width: 100%;
|
|
||||||
height: 20px;
|
|
||||||
background-color: #66ccff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar .navbar-item {
|
|
||||||
margin: auto;
|
|
||||||
position: relative;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal-centre {
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vertical-centre {
|
|
||||||
position: relative;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
min-height: calc(100% - 58px - 40px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
height: 58px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script type="module" defer>
|
|
||||||
import init from "/static/wasm.js";
|
|
||||||
import { run_app } from "/static/wasm.js";
|
|
||||||
await init();
|
|
||||||
await run_app();
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,9 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<!-- 404 Not found -->
|
|
||||||
<h1>404 Not found</h1>
|
|
||||||
<p>The requested URL was not found on this server.</p>
|
|
||||||
<p>Additionally, a 404 Not Found
|
|
||||||
error was encountered while trying to use an ErrorDocument to handle the request.</p>
|
|
||||||
</body>
|
|
2
run.sh
2
run.sh
@ -6,4 +6,4 @@ else
|
|||||||
cargo make install-debug
|
cargo make install-debug
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd build && ./webgateway-be
|
cd build && ./axum-websockify 8080 $2 --web `pwd`
|
||||||
|
11
webvnc/.appveyor.yml
Normal file
11
webvnc/.appveyor.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
install:
|
||||||
|
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||||
|
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
|
||||||
|
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||||
|
- rustc -V
|
||||||
|
- cargo -V
|
||||||
|
|
||||||
|
build: false
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- cargo test --locked
|
6
webvnc/.gitignore
vendored
Normal file
6
webvnc/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
bin/
|
||||||
|
pkg/
|
||||||
|
wasm-pack.log
|
50
webvnc/Cargo.toml
Normal file
50
webvnc/Cargo.toml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
[package]
|
||||||
|
name = "webvnc"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Jovi Hsu <jv.hsu@outlook.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = "0.2.83"
|
||||||
|
js-sys = "0.3"
|
||||||
|
anyhow="1"
|
||||||
|
# bytes="1"
|
||||||
|
|
||||||
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||||
|
# code size when deploying.
|
||||||
|
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3.22"
|
||||||
|
features = [
|
||||||
|
"BinaryType",
|
||||||
|
"Blob",
|
||||||
|
"CanvasRenderingContext2d",
|
||||||
|
"Document",
|
||||||
|
"ErrorEvent",
|
||||||
|
"FileReader",
|
||||||
|
"HtmlCanvasElement",
|
||||||
|
"ImageData",
|
||||||
|
"Location",
|
||||||
|
"KeyboardEvent",
|
||||||
|
"MouseEvent",
|
||||||
|
"MessageEvent",
|
||||||
|
"ProgressEvent",
|
||||||
|
"Window",
|
||||||
|
"WebSocket",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.3.13"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
opt-level = "s"
|
25
webvnc/Makefile.toml
Normal file
25
webvnc/Makefile.toml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[tasks.build-debug]
|
||||||
|
command = "wasm-pack"
|
||||||
|
args = ["build", "--target", "web", "--out-name", "webvnc", "--out-dir", "./pkg", "--dev"]
|
||||||
|
|
||||||
|
[tasks.build-release]
|
||||||
|
command = "wasm-pack"
|
||||||
|
args = ["build", "--target", "web", "--out-name", "webvnc", "--out-dir", "./pkg"]
|
||||||
|
|
||||||
|
[tasks.install-debug]
|
||||||
|
dependencies=["build-debug", "install_wasm", "install_html"]
|
||||||
|
|
||||||
|
[tasks.install-release]
|
||||||
|
dependencies=["build-release", "install_wasm", "install_html"]
|
||||||
|
|
||||||
|
[tasks.install_wasm]
|
||||||
|
script = '''
|
||||||
|
mkdir -p $INSTALL_PATH
|
||||||
|
cp ./pkg/webvnc.js $INSTALL_PATH
|
||||||
|
cp ./pkg/webvnc_bg.wasm $INSTALL_PATH
|
||||||
|
'''
|
||||||
|
|
||||||
|
[tasks.install_html]
|
||||||
|
script = '''
|
||||||
|
cp asserts/* $INSTALL_PATH
|
||||||
|
'''
|
52
webvnc/asserts/vnc.html
Normal file
52
webvnc/asserts/vnc.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Web Gateway</title>
|
||||||
|
<style type="text/css">
|
||||||
|
.navbar {
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #66ccff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .navbar-item {
|
||||||
|
margin: auto;
|
||||||
|
position: relative;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-centre {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-centre {
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
min-height: calc(100% - 58px - 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
height: 58px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="module" defer>
|
||||||
|
import init from "/webvnc.js";
|
||||||
|
await init();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="horizontal-centre vertical-centre"><canvas id="vnc-canvas" tabIndex=1></canvas></div>
|
||||||
|
</body>
|
310
webvnc/src/lib.rs
Normal file
310
webvnc/src/lib.rs
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
mod utils;
|
||||||
|
mod vnc;
|
||||||
|
|
||||||
|
use vnc::{MouseEventType, Vnc};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::{Clamped, JsCast};
|
||||||
|
use web_sys::{
|
||||||
|
ErrorEvent, HtmlCanvasElement, ImageData, KeyboardEvent, MessageEvent, MouseEvent, WebSocket,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! console_log {
|
||||||
|
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
||||||
|
fn cancelInterval(token: f64);
|
||||||
|
#[wasm_bindgen(js_namespace = console)]
|
||||||
|
pub fn log(s: &str);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind_mouse_and_key(vnc: &Vnc, canvas: &HtmlCanvasElement) {
|
||||||
|
let _window = web_sys::window().unwrap();
|
||||||
|
let handler = vnc.clone();
|
||||||
|
let key_down = move |e: KeyboardEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
e.stop_propagation();
|
||||||
|
handler.key_press(e, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = Box::new(key_down) as Box<dyn FnMut(_)>;
|
||||||
|
|
||||||
|
let cb = Closure::wrap(handler);
|
||||||
|
|
||||||
|
canvas
|
||||||
|
.add_event_listener_with_callback("keydown", cb.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
cb.forget();
|
||||||
|
|
||||||
|
let handler = vnc.clone();
|
||||||
|
let key_up = move |e: KeyboardEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
e.stop_propagation();
|
||||||
|
handler.key_press(e, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = Box::new(key_up) as Box<dyn FnMut(_)>;
|
||||||
|
|
||||||
|
let cb = Closure::wrap(handler);
|
||||||
|
|
||||||
|
canvas
|
||||||
|
.add_event_listener_with_callback("keyup", cb.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
cb.forget();
|
||||||
|
|
||||||
|
// On a conventional mouse, buttons 1, 2, and 3 correspond to the left,
|
||||||
|
// middle, and right buttons on the mouse. On a wheel mouse, each step
|
||||||
|
// of the wheel upwards is represented by a press and release of button
|
||||||
|
// 4, and each step downwards is represented by a press and release of
|
||||||
|
// button 5.
|
||||||
|
|
||||||
|
// to do:
|
||||||
|
// calculate relation position
|
||||||
|
let handler = vnc.clone();
|
||||||
|
let mouse_move = move |e: MouseEvent| {
|
||||||
|
e.stop_propagation();
|
||||||
|
handler.mouse_event(e, MouseEventType::MouseMove);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = Box::new(mouse_move) as Box<dyn FnMut(_)>;
|
||||||
|
|
||||||
|
let cb = Closure::wrap(handler);
|
||||||
|
|
||||||
|
canvas
|
||||||
|
.add_event_listener_with_callback("mousemove", cb.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
cb.forget();
|
||||||
|
|
||||||
|
let handler = vnc.clone();
|
||||||
|
let mouse_down = move |e: MouseEvent| {
|
||||||
|
e.stop_propagation();
|
||||||
|
handler.mouse_event(e, MouseEventType::MouseDown);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = Box::new(mouse_down) as Box<dyn FnMut(_)>;
|
||||||
|
|
||||||
|
let cb = Closure::wrap(handler);
|
||||||
|
|
||||||
|
canvas
|
||||||
|
.add_event_listener_with_callback("mousedown", cb.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
cb.forget();
|
||||||
|
|
||||||
|
let handler = vnc.clone();
|
||||||
|
let mouse_up = move |e: MouseEvent| {
|
||||||
|
e.stop_propagation();
|
||||||
|
handler.mouse_event(e, MouseEventType::MouseUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = Box::new(mouse_up) as Box<dyn FnMut(_)>;
|
||||||
|
|
||||||
|
let cb = Closure::wrap(handler);
|
||||||
|
|
||||||
|
canvas
|
||||||
|
.add_event_listener_with_callback("mouseup", cb.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
cb.forget();
|
||||||
|
|
||||||
|
let get_context_menu = move |e: MouseEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
e.stop_propagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = Box::new(get_context_menu) as Box<dyn FnMut(_)>;
|
||||||
|
|
||||||
|
let cb = Closure::wrap(handler);
|
||||||
|
|
||||||
|
canvas
|
||||||
|
.add_event_listener_with_callback("contextmenu", cb.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
cb.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_canvas() -> HtmlCanvasElement {
|
||||||
|
let document = web_sys::window().unwrap().document().unwrap();
|
||||||
|
let canvas = document.get_element_by_id("vnc-canvas").unwrap();
|
||||||
|
let canvas: web_sys::HtmlCanvasElement = canvas
|
||||||
|
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||||
|
.map_err(|_| ())
|
||||||
|
.unwrap();
|
||||||
|
canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_canvas(vnc: &Vnc, x: u16, y: u16) {
|
||||||
|
let canvas = find_canvas();
|
||||||
|
|
||||||
|
// set hight & width
|
||||||
|
canvas.set_height(y as u32);
|
||||||
|
canvas.set_width(x as u32);
|
||||||
|
|
||||||
|
// bind keyboard & mouse
|
||||||
|
bind_mouse_and_key(vnc, &canvas);
|
||||||
|
|
||||||
|
let ctx = canvas
|
||||||
|
.get_context("2d")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<web_sys::CanvasRenderingContext2d>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ctx.rect(0 as f64, 0 as f64, x as f64, y as f64);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vnc_out_handler(ws: &WebSocket, vnc: &Vnc) {
|
||||||
|
let out = vnc.get_output();
|
||||||
|
if !out.is_empty() {
|
||||||
|
for ref o in out {
|
||||||
|
match o {
|
||||||
|
vnc::VncOutput::Err(err) => {
|
||||||
|
console_log!("Err {}", err);
|
||||||
|
}
|
||||||
|
vnc::VncOutput::WsBuf(buf) => match ws.send_with_u8_array(&buf) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => console_log!("error sending message: {:?}", err),
|
||||||
|
},
|
||||||
|
// vnc::VncOutput::RequirePassword => {
|
||||||
|
// self.request_password = true;
|
||||||
|
// }
|
||||||
|
vnc::VncOutput::RenderCanvas(cr) => {
|
||||||
|
let canvas = find_canvas();
|
||||||
|
let ctx = canvas
|
||||||
|
.get_context("2d")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<web_sys::CanvasRenderingContext2d>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match cr.type_ {
|
||||||
|
1 => {
|
||||||
|
//copy
|
||||||
|
let sx = (cr.data[0] as u16) << 8 | cr.data[1] as u16;
|
||||||
|
let sy = (cr.data[2] as u16) << 8 | cr.data[3] as u16;
|
||||||
|
|
||||||
|
let _ = ctx.
|
||||||
|
draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
|
||||||
|
&canvas,
|
||||||
|
sx as f64,
|
||||||
|
sy as f64,
|
||||||
|
cr.width as f64,
|
||||||
|
cr.height as f64,
|
||||||
|
cr.x as f64,
|
||||||
|
cr.y as f64,
|
||||||
|
cr.width as f64,
|
||||||
|
cr.height as f64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let data = ImageData::new_with_u8_clamped_array_and_sh(
|
||||||
|
Clamped(&cr.data),
|
||||||
|
cr.width as u32,
|
||||||
|
cr.height as u32,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// ConsoleService::log(&format!(
|
||||||
|
// "renderring at ({}, {}), width {}, height {}",
|
||||||
|
// cr.x, cr.y, cr.width, cr.height
|
||||||
|
// ));
|
||||||
|
let _ = ctx.put_image_data(&data, cr.x as f64, cr.y as f64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vnc::VncOutput::SetCanvas(x, y) => {
|
||||||
|
set_canvas(&vnc, *x, *y);
|
||||||
|
|
||||||
|
let vnc_cloned = vnc.clone();
|
||||||
|
let ws_cloned = ws.clone();
|
||||||
|
let mut incremental = 0;
|
||||||
|
|
||||||
|
// set a interval for fps enhance
|
||||||
|
let refresh = move || {
|
||||||
|
vnc_cloned.require_frame(incremental);
|
||||||
|
incremental = if incremental > 0 { incremental } else { 1 };
|
||||||
|
vnc_out_handler(&ws_cloned, &vnc_cloned);
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = Box::new(refresh) as Box<dyn FnMut()>;
|
||||||
|
|
||||||
|
let cb: wasm_bindgen::prelude::Closure<(dyn FnMut() + 'static)> =
|
||||||
|
Closure::wrap(handler);
|
||||||
|
|
||||||
|
setInterval(&cb, 20);
|
||||||
|
cb.forget();
|
||||||
|
}
|
||||||
|
// vnc::VncOutput::SetClipboard(text) => {
|
||||||
|
// self.clipboard
|
||||||
|
// .borrow_mut()
|
||||||
|
// .as_mut()
|
||||||
|
// .unwrap()
|
||||||
|
// .send_message(components::clipboard::ClipboardMsg::UpdateClipboard(text));
|
||||||
|
// // ConsoleService::log(&self.error_msg);
|
||||||
|
// }
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_websocket() -> Result<(), JsValue> {
|
||||||
|
// connect
|
||||||
|
let url = format!(
|
||||||
|
"{scheme}://{host}/websockify",
|
||||||
|
scheme = if web_sys::window()
|
||||||
|
.unwrap()
|
||||||
|
.location()
|
||||||
|
.protocol()?
|
||||||
|
.starts_with("https")
|
||||||
|
{
|
||||||
|
"wss"
|
||||||
|
} else {
|
||||||
|
"ws"
|
||||||
|
},
|
||||||
|
host = web_sys::window().unwrap().location().host()?
|
||||||
|
);
|
||||||
|
let ws = WebSocket::new_with_str(&url, "binary")?;
|
||||||
|
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
|
||||||
|
|
||||||
|
let vnc = Vnc::new();
|
||||||
|
|
||||||
|
let cloned_vnc = vnc.clone();
|
||||||
|
// on message
|
||||||
|
let cloned_ws = ws.clone();
|
||||||
|
|
||||||
|
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
|
||||||
|
if let Ok(abuf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
|
||||||
|
let array = js_sys::Uint8Array::new(&abuf);
|
||||||
|
// let mut canvas_ctx = None;
|
||||||
|
cloned_vnc.do_input(array.to_vec());
|
||||||
|
vnc_out_handler(&cloned_ws, &cloned_vnc);
|
||||||
|
} else {
|
||||||
|
console_log!("message event, received Unknown: {:?}", e.data());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
|
||||||
|
// forget the callback to keep it alive
|
||||||
|
onmessage_callback.forget();
|
||||||
|
|
||||||
|
// onerror
|
||||||
|
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
|
||||||
|
console_log!("error event: {:?}", e);
|
||||||
|
});
|
||||||
|
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
|
||||||
|
onerror_callback.forget();
|
||||||
|
|
||||||
|
// onopen
|
||||||
|
let onopen_callback = Closure::<dyn FnMut()>::new(move || {
|
||||||
|
console_log!("socket opened");
|
||||||
|
});
|
||||||
|
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
|
||||||
|
onopen_callback.forget();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(start)]
|
||||||
|
pub fn run_app() -> Result<(), JsValue> {
|
||||||
|
start_websocket()
|
||||||
|
}
|
10
webvnc/src/utils.rs
Normal file
10
webvnc/src/utils.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
pub fn set_panic_hook() {
|
||||||
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
|
// `set_panic_hook` function at least once during initialization, and then
|
||||||
|
// we will get better error messages if our code ever panics.
|
||||||
|
//
|
||||||
|
// For more details see
|
||||||
|
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||||
|
#[cfg(feature = "console_error_panic_hook")]
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
}
|
@ -1,5 +1,18 @@
|
|||||||
|
mod des;
|
||||||
|
mod vnc;
|
||||||
|
mod x11cursor;
|
||||||
|
mod x11keyboard;
|
||||||
|
|
||||||
|
pub enum MouseEventType {
|
||||||
|
MouseDown,
|
||||||
|
MouseUp,
|
||||||
|
MouseMove,
|
||||||
|
}
|
||||||
|
|
||||||
use std::{rc::Rc, sync::Mutex};
|
use std::{rc::Rc, sync::Mutex};
|
||||||
|
|
||||||
|
use crate::{console_log, log};
|
||||||
|
|
||||||
pub struct CanvasData {
|
pub struct CanvasData {
|
||||||
pub type_: u32,
|
pub type_: u32,
|
||||||
pub x: u16,
|
pub x: u16,
|
||||||
@ -9,47 +22,23 @@ pub struct CanvasData {
|
|||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MouseEventType {
|
pub enum VncOutput {
|
||||||
MouseDown,
|
|
||||||
MouseUp,
|
|
||||||
MouseMove,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ProtocalHandlerOutput {
|
|
||||||
WsBuf(Vec<u8>),
|
WsBuf(Vec<u8>),
|
||||||
Err(String),
|
Err(String),
|
||||||
RequireUsername,
|
|
||||||
RequirePassword,
|
RequirePassword,
|
||||||
SetCanvas(u16, u16),
|
SetCanvas(u16, u16),
|
||||||
RenderCanvas(CanvasData),
|
RenderCanvas(CanvasData),
|
||||||
SetClipboard(String),
|
SetClipboard(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProtocalHandler<T>
|
pub struct Vnc {
|
||||||
where
|
inner: Rc<Mutex<vnc::Vnc>>,
|
||||||
T: ProtocalImpl,
|
|
||||||
{
|
|
||||||
inner: Rc<Mutex<T>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for ProtocalHandler<T>
|
impl Vnc {
|
||||||
where
|
|
||||||
T: ProtocalImpl,
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: self.inner.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ProtocalHandler<T>
|
|
||||||
where
|
|
||||||
T: ProtocalImpl,
|
|
||||||
{
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Rc::new(Mutex::new(T::new())),
|
inner: Rc::new(Mutex::new(vnc::Vnc::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +46,7 @@ where
|
|||||||
self.inner.as_ref().lock().unwrap().do_input(input);
|
self.inner.as_ref().lock().unwrap().do_input(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_output(&self) -> Vec<ProtocalHandlerOutput> {
|
pub fn get_output(&self) -> Vec<VncOutput> {
|
||||||
self.inner.as_ref().lock().unwrap().get_output()
|
self.inner.as_ref().lock().unwrap().get_output()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,18 +58,10 @@ where
|
|||||||
.set_credential(username, password);
|
.set_credential(username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_clipboard(&mut self, text: &str) {
|
pub fn set_clipboard(&self, text: &str) {
|
||||||
self.inner.as_ref().lock().unwrap().set_clipboard(text);
|
self.inner.as_ref().lock().unwrap().set_clipboard(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_resolution(&self, width: u16, height: u16) {
|
|
||||||
self.inner
|
|
||||||
.as_ref()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_resolution(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn require_frame(&self, incremental: u8) {
|
pub fn require_frame(&self, incremental: u8) {
|
||||||
self.inner
|
self.inner
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -98,18 +79,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ProtocalImpl {
|
impl Clone for Vnc {
|
||||||
fn new() -> Self
|
fn clone(&self) -> Self {
|
||||||
where
|
Self {
|
||||||
Self: Sized;
|
inner: self.inner.clone(),
|
||||||
fn do_input(&mut self, input: Vec<u8>);
|
}
|
||||||
fn get_output(&mut self) -> Vec<ProtocalHandlerOutput>;
|
}
|
||||||
fn set_credential(&mut self, username: &str, password: &str);
|
|
||||||
fn set_clipboard(&mut self, text: &str);
|
|
||||||
fn set_resolution(&mut self, width: u16, height: u16);
|
|
||||||
fn key_press(&mut self, key: web_sys::KeyboardEvent, down: bool);
|
|
||||||
fn mouse_event(&mut self, mouse: web_sys::MouseEvent, et: MouseEventType);
|
|
||||||
fn require_frame(&mut self, incremental: u8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StreamReader {
|
pub struct StreamReader {
|
@ -1,11 +1,10 @@
|
|||||||
use crate::protocal::common::MouseEventType;
|
use super::*;
|
||||||
|
use super::{des, x11cursor::MouseUtils, x11keyboard, MouseEventType};
|
||||||
use super::{super::common::*, des, x11cursor::MouseUtils, x11keyboard};
|
use crate::{console_log, log};
|
||||||
use yew::services::ConsoleService;
|
|
||||||
|
|
||||||
const VNC_RFB33: &[u8; 12] = b"RFB 003.003\n";
|
const VNC_RFB33: &[u8; 12] = b"RFB 003.003\n";
|
||||||
const VNC_RFB37: &[u8; 12] = b"RFB 003.007\n";
|
// const VNC_RFB37: &[u8; 12] = b"RFB 003.007\n";
|
||||||
const VNC_RFB38: &[u8; 12] = b"RFB 003.008\n";
|
// const VNC_RFB38: &[u8; 12] = b"RFB 003.008\n";
|
||||||
const VNC_VER_UNSUPPORTED: &str = "unsupported version";
|
const VNC_VER_UNSUPPORTED: &str = "unsupported version";
|
||||||
const VNC_FAILED: &str = "Connection failed with unknow reason";
|
const VNC_FAILED: &str = "Connection failed with unknow reason";
|
||||||
|
|
||||||
@ -54,7 +53,7 @@ pub enum ServerMessage {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VncHandler {
|
pub struct Vnc {
|
||||||
state: VncState,
|
state: VncState,
|
||||||
// supported_versions: Vec<u8>,
|
// supported_versions: Vec<u8>,
|
||||||
supported_encodings: Vec<VncEncoding>,
|
supported_encodings: Vec<VncEncoding>,
|
||||||
@ -71,11 +70,11 @@ pub struct VncHandler {
|
|||||||
num_rect_left: u16,
|
num_rect_left: u16,
|
||||||
padding_rect: Option<VncRect>,
|
padding_rect: Option<VncRect>,
|
||||||
outbuf: Vec<u8>,
|
outbuf: Vec<u8>,
|
||||||
outs: Vec<ProtocalHandlerOutput>,
|
outs: Vec<VncOutput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProtocalImpl for VncHandler {
|
impl Vnc {
|
||||||
fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: VncState::Init,
|
state: VncState::Init,
|
||||||
supported_encodings: vec![
|
supported_encodings: vec![
|
||||||
@ -105,7 +104,8 @@ impl ProtocalImpl for VncHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_input(&mut self, input: Vec<u8>) {
|
|
||||||
|
pub fn do_input(&mut self, input: Vec<u8>) {
|
||||||
// ConsoleService::info(&format!(
|
// ConsoleService::info(&format!(
|
||||||
// "VNC input {}, left {}, require {}",
|
// "VNC input {}, left {}, require {}",
|
||||||
// input.len(),
|
// input.len(),
|
||||||
@ -123,15 +123,15 @@ impl ProtocalImpl for VncHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_output(&mut self) -> Vec<ProtocalHandlerOutput> {
|
pub fn get_output(&mut self) -> Vec<VncOutput> {
|
||||||
if let ServerMessage::None = self.msg_handling {
|
if let ServerMessage::None = self.msg_handling {
|
||||||
let mut out = Vec::with_capacity(self.outs.len());
|
let mut out = Vec::with_capacity(self.outs.len());
|
||||||
// ConsoleService::log(&format!("Get {} output", self.outs.len()));
|
// console_log!("Get {} output", self.outs.len());
|
||||||
for o in self.outs.drain(..) {
|
for o in self.outs.drain(..) {
|
||||||
out.push(o);
|
out.push(o);
|
||||||
}
|
}
|
||||||
if !self.outbuf.is_empty() {
|
if !self.outbuf.is_empty() {
|
||||||
out.push(ProtocalHandlerOutput::WsBuf(self.outbuf.clone()));
|
out.push(VncOutput::WsBuf(self.outbuf.clone()));
|
||||||
self.outbuf.clear();
|
self.outbuf.clear();
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
@ -140,7 +140,7 @@ impl ProtocalImpl for VncHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_credential(&mut self, _username: &str, password: &str) {
|
pub fn set_credential(&mut self, _username: &str, password: &str) {
|
||||||
// referring
|
// referring
|
||||||
// https://github.com/whitequark/rust-vnc/blob/0697238f2706dd34a9a95c1640e385f6d8c02961/src/client.rs
|
// https://github.com/whitequark/rust-vnc/blob/0697238f2706dd34a9a95c1640e385f6d8c02961/src/client.rs
|
||||||
// strange behavior
|
// strange behavior
|
||||||
@ -159,7 +159,7 @@ impl ProtocalImpl for VncHandler {
|
|||||||
}
|
}
|
||||||
*key_i = cs;
|
*key_i = cs;
|
||||||
}
|
}
|
||||||
// ConsoleService::log(&format!("challenge {:x?}", self.challenge));
|
// console_log!("challenge {:x?}", self.challenge);
|
||||||
let output = des::encrypt(&self.challenge, &key);
|
let output = des::encrypt(&self.challenge, &key);
|
||||||
|
|
||||||
self.outbuf.extend_from_slice(&output);
|
self.outbuf.extend_from_slice(&output);
|
||||||
@ -167,15 +167,11 @@ impl ProtocalImpl for VncHandler {
|
|||||||
self.require = 4; // the auth result message length
|
self.require = 4; // the auth result message length
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_clipboard(&mut self, text: &str) {
|
pub fn set_clipboard(&mut self, text: &str) {
|
||||||
self.send_client_cut_text(text);
|
self.send_client_cut_text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_resolution(&mut self, _width: u16, _height: u16) {
|
pub fn key_press(&mut self, key: web_sys::KeyboardEvent, down: bool) {
|
||||||
// VNC client doen't support resolution change
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_press(&mut self, key: web_sys::KeyboardEvent, down: bool) {
|
|
||||||
if self.state != VncState::Connected {
|
if self.state != VncState::Connected {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -183,7 +179,7 @@ impl ProtocalImpl for VncHandler {
|
|||||||
self.send_key_event(key, down);
|
self.send_key_event(key, down);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_event(&mut self, mouse: web_sys::MouseEvent, et: MouseEventType) {
|
pub fn mouse_event(&mut self, mouse: web_sys::MouseEvent, et: MouseEventType) {
|
||||||
if self.state != VncState::Connected {
|
if self.state != VncState::Connected {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -191,7 +187,7 @@ impl ProtocalImpl for VncHandler {
|
|||||||
self.send_pointer_event(x, y, mask);
|
self.send_pointer_event(x, y, mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_frame(&mut self, incremental: u8) {
|
pub fn require_frame(&mut self, incremental: u8) {
|
||||||
if 0 == incremental {
|
if 0 == incremental {
|
||||||
// first frame
|
// first frame
|
||||||
// set the client encoding
|
// set the client encoding
|
||||||
@ -204,7 +200,7 @@ impl ProtocalImpl for VncHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl VncHandler {
|
impl Vnc {
|
||||||
fn read_u8(&mut self) -> u8 {
|
fn read_u8(&mut self) -> u8 {
|
||||||
self.reader.read_u8()
|
self.reader.read_u8()
|
||||||
}
|
}
|
||||||
@ -249,11 +245,11 @@ impl VncHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VncHandler {
|
impl Vnc {
|
||||||
fn disconnect_with_err(&mut self, err: &str) {
|
fn disconnect_with_err(&mut self, err: &str) {
|
||||||
ConsoleService::error(err);
|
console_log!("{:#?}", err);
|
||||||
self.state = VncState::Disconnected;
|
self.state = VncState::Disconnected;
|
||||||
self.outs.push(ProtocalHandlerOutput::Err(err.to_string()));
|
self.outs.push(VncOutput::Err(err.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_client_initilize(&mut self) {
|
fn send_client_initilize(&mut self) {
|
||||||
@ -309,7 +305,7 @@ impl VncHandler {
|
|||||||
sw.write_u16(0); // padding
|
sw.write_u16(0); // padding
|
||||||
sw.write_u32(key); // key
|
sw.write_u32(key); // key
|
||||||
|
|
||||||
// ConsoleService::log(&format!("send key event {:x?} {:?}", key, down));
|
// console_log!("send key event {:x?} {:?}", key, down);
|
||||||
self.outbuf.extend_from_slice(&out);
|
self.outbuf.extend_from_slice(&out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,7 +325,7 @@ impl VncHandler {
|
|||||||
sw.write_u16(x); // x
|
sw.write_u16(x); // x
|
||||||
sw.write_u16(y); // y
|
sw.write_u16(y); // y
|
||||||
|
|
||||||
// ConsoleService::log(&format!("send mouse event {:x?} {:x?} {:#08b}", x, y, mask));
|
// console_log!("send mouse event {:x?} {:x?} {:#08b}", x, y, mask);
|
||||||
self.outbuf.extend_from_slice(&out);
|
self.outbuf.extend_from_slice(&out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,7 +347,7 @@ impl VncHandler {
|
|||||||
sw.write_u32(len); // length
|
sw.write_u32(len); // length
|
||||||
sw.write_string(text); // text
|
sw.write_string(text); // text
|
||||||
|
|
||||||
// ConsoleService::log(&format!("send client cut text {:?}", len));
|
// console_log!("send client cut text {:?}", len);
|
||||||
self.outbuf.extend_from_slice(&out);
|
self.outbuf.extend_from_slice(&out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,7 +359,7 @@ impl VncHandler {
|
|||||||
// 2 CARD16 width
|
// 2 CARD16 width
|
||||||
// 2 CARD16 height
|
// 2 CARD16 height
|
||||||
fn framebuffer_update_request(&mut self, incremental: u8) {
|
fn framebuffer_update_request(&mut self, incremental: u8) {
|
||||||
// ConsoleService::log(&format!("VNC: framebuffer_update_request {}", incremental));
|
// console_log!("VNC: framebuffer_update_request {}", incremental);
|
||||||
let mut out: Vec<u8> = Vec::new();
|
let mut out: Vec<u8> = Vec::new();
|
||||||
let mut sw = StreamWriter::new(&mut out);
|
let mut sw = StreamWriter::new(&mut out);
|
||||||
sw.write_u8(3);
|
sw.write_u8(3);
|
||||||
@ -407,7 +403,7 @@ impl VncHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn do_authenticate(&mut self) {
|
fn do_authenticate(&mut self) {
|
||||||
// ConsoleService::log(&format!("VNC: do_authenticate {}", self.reader.remain()));
|
// console_log!("VNC: do_authenticate {}", self.reader.remain());
|
||||||
if self.security_type == SecurityType::Invalid {
|
if self.security_type == SecurityType::Invalid {
|
||||||
let auth_type = self.read_u32();
|
let auth_type = self.read_u32();
|
||||||
match auth_type {
|
match auth_type {
|
||||||
@ -426,13 +422,13 @@ impl VncHandler {
|
|||||||
let mut challenge = [0u8; 16];
|
let mut challenge = [0u8; 16];
|
||||||
self.read_exact(&mut challenge, 16);
|
self.read_exact(&mut challenge, 16);
|
||||||
self.challenge = challenge;
|
self.challenge = challenge;
|
||||||
self.outs.push(ProtocalHandlerOutput::RequirePassword);
|
self.outs.push(VncOutput::RequirePassword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_auth_result(&mut self) {
|
fn handle_auth_result(&mut self) {
|
||||||
let response = self.read_u32();
|
let response = self.read_u32();
|
||||||
ConsoleService::log(&format!("Auth resp {}", response));
|
console_log!("Auth resp {}", response);
|
||||||
match response {
|
match response {
|
||||||
0 => self.send_client_initilize(),
|
0 => self.send_client_initilize(),
|
||||||
1 => {
|
1 => {
|
||||||
@ -456,12 +452,12 @@ impl VncHandler {
|
|||||||
self.read_exact(&mut pfb, 16);
|
self.read_exact(&mut pfb, 16);
|
||||||
// This pixel format will be used unless the client requests a different format using the SetPixelFormat message
|
// This pixel format will be used unless the client requests a different format using the SetPixelFormat message
|
||||||
self.pf = (&pfb).into();
|
self.pf = (&pfb).into();
|
||||||
ConsoleService::log(&format!("VNC: {}x{}", self.width, self.height));
|
console_log!("VNC: {}x{}", self.width, self.height);
|
||||||
self.name = self.read_string_l32();
|
self.name = self.read_string_l32();
|
||||||
self.state = VncState::Connected;
|
self.state = VncState::Connected;
|
||||||
self.require = 1; // any message from sever will be handled
|
self.require = 1; // any message from sever will be handled
|
||||||
self.outs
|
self.outs
|
||||||
.push(ProtocalHandlerOutput::SetCanvas(self.width, self.height));
|
.push(VncOutput::SetCanvas(self.width, self.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_server_message(&mut self) {
|
fn handle_server_message(&mut self) {
|
||||||
@ -491,7 +487,7 @@ impl VncHandler {
|
|||||||
fn handle_framebuffer_update(&mut self) {
|
fn handle_framebuffer_update(&mut self) {
|
||||||
let _padding = self.read_u8();
|
let _padding = self.read_u8();
|
||||||
self.num_rect_left = self.read_u16();
|
self.num_rect_left = self.read_u16();
|
||||||
// ConsoleService::log(&format!("VNC: {} rects", self.num_rects_left));
|
// console_log!("VNC: {} rects", self.num_rects_left);
|
||||||
self.require = 12; // the length of the first rectangle hdr
|
self.require = 12; // the length of the first rectangle hdr
|
||||||
self.msg_handling = ServerMessage::FramebufferUpdate;
|
self.msg_handling = ServerMessage::FramebufferUpdate;
|
||||||
}
|
}
|
||||||
@ -562,7 +558,7 @@ impl VncHandler {
|
|||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
self.outs
|
self.outs
|
||||||
.push(ProtocalHandlerOutput::RenderCanvas(CanvasData {
|
.push(VncOutput::RenderCanvas(CanvasData {
|
||||||
type_: rect.encoding_type,
|
type_: rect.encoding_type,
|
||||||
x: rect.x,
|
x: rect.x,
|
||||||
y: rect.y,
|
y: rect.y,
|
||||||
@ -637,14 +633,14 @@ impl VncHandler {
|
|||||||
}
|
}
|
||||||
self.require = self.read_u32() as usize;
|
self.require = self.read_u32() as usize;
|
||||||
self.msg_handling = ServerMessage::ServerCutText;
|
self.msg_handling = ServerMessage::ServerCutText;
|
||||||
ConsoleService::log(&format!("VNC: ServerCutText {} bytes", self.require));
|
console_log!("VNC: ServerCutText {} bytes", self.require);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_cut_text(&mut self) {
|
fn read_cut_text(&mut self) {
|
||||||
let text = self.read_string(self.require);
|
let text = self.read_string(self.require);
|
||||||
self.require = 1;
|
self.require = 1;
|
||||||
self.msg_handling = ServerMessage::None;
|
self.msg_handling = ServerMessage::None;
|
||||||
self.outs.push(ProtocalHandlerOutput::SetClipboard(text));
|
self.outs.push(VncOutput::SetClipboard(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_raw_encoding(&mut self, x: u16, y: u16, width: u16, height: u16) {
|
fn handle_raw_encoding(&mut self, x: u16, y: u16, width: u16, height: u16) {
|
||||||
@ -659,7 +655,7 @@ impl VncHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_copy_rect_encoding(&mut self, x: u16, y: u16, width: u16, height: u16) {
|
fn handle_copy_rect_encoding(&mut self, x: u16, y: u16, width: u16, height: u16) {
|
||||||
ConsoleService::log(&format!("VNC: CopyRect {} {} {} {}", x, y, width, height));
|
console_log!("VNC: CopyRect {} {} {} {}", x, y, width, height);
|
||||||
self.require = 4;
|
self.require = 4;
|
||||||
self.padding_rect = Some(VncRect {
|
self.padding_rect = Some(VncRect {
|
||||||
x,
|
x,
|
@ -1,4 +1,4 @@
|
|||||||
use crate::protocal::common::MouseEventType;
|
use super::MouseEventType;
|
||||||
|
|
||||||
pub struct MouseUtils {
|
pub struct MouseUtils {
|
||||||
down: bool,
|
down: bool,
|
13
webvnc/tests/web.rs
Normal file
13
webvnc/tests/web.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//! Test suite for the Web and headless browsers.
|
||||||
|
|
||||||
|
#![cfg(target_arch = "wasm32")]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen_test;
|
||||||
|
use wasm_bindgen_test::*;
|
||||||
|
|
||||||
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn pass() {
|
||||||
|
assert_eq!(1 + 1, 2);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user