mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-07-08 01:55:01 +00:00
refactor(web-client): refactor iron-remote-gui
into iron-remote-desktop
(#722)
This commit is contained in:
parent
184cfd24ae
commit
0ff1ed8de5
86 changed files with 4950 additions and 330 deletions
|
@ -168,12 +168,18 @@ WebAssembly high-level bindings targeting web browsers.
|
||||||
|
|
||||||
This crate is an **API Boundary** (WASM module).
|
This crate is an **API Boundary** (WASM module).
|
||||||
|
|
||||||
#### [`web-client/iron-remote-gui`](./web-client/iron-remote-gui)
|
#### [`web-client/iron-remote-desktop`](./web-client/iron-remote-desktop)
|
||||||
|
|
||||||
Core frontend UI used by `iron-svelte-client` as a Web Component.
|
Core frontend UI used by `iron-svelte-client` as a Web Component.
|
||||||
|
|
||||||
This crate is an **API Boundary**.
|
This crate is an **API Boundary**.
|
||||||
|
|
||||||
|
#### [`web-client/iron-remote-desktop-rdp`](./web-client/iron-remote-desktop-rdp)
|
||||||
|
|
||||||
|
Implementation of the TypeScript interfaces exposed by WebAssembly bindings from `ironrdp-web` and used by `iron-svelte-client`.
|
||||||
|
|
||||||
|
This crate is an **API Boundary**.
|
||||||
|
|
||||||
#### [`web-client/iron-svelte-client`](./web-client/iron-svelte-client)
|
#### [`web-client/iron-svelte-client`](./web-client/iron-svelte-client)
|
||||||
|
|
||||||
Web-based frontend using `Svelte` and `Material` frameworks.
|
Web-based frontend using `Svelte` and `Material` frameworks.
|
||||||
|
|
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -2809,6 +2809,8 @@ dependencies = [
|
||||||
"resize",
|
"resize",
|
||||||
"rgb",
|
"rgb",
|
||||||
"semver",
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"softbuffer",
|
"softbuffer",
|
||||||
"tap",
|
"tap",
|
||||||
|
@ -4668,6 +4670,17 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-wasm-bindgen"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_bytes"
|
name = "serde_bytes"
|
||||||
version = "0.11.17"
|
version = "0.11.17"
|
||||||
|
|
|
@ -45,6 +45,8 @@ web-sys = { version = "0.3", features = ["HtmlCanvasElement"] }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
gloo-net = { version = "0.6", default-features = false, features = ["websocket", "http", "io-util"] }
|
gloo-net = { version = "0.6", default-features = false, features = ["websocket", "http", "io-util"] }
|
||||||
gloo-timers = { version = "0.3", default-features = false, features = ["futures"] }
|
gloo-timers = { version = "0.3", default-features = false, features = ["futures"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde-wasm-bindgen = "0.6"
|
||||||
tracing-web = "0.1"
|
tracing-web = "0.1"
|
||||||
|
|
||||||
# Rendering
|
# Rendering
|
||||||
|
|
|
@ -137,7 +137,7 @@ impl WasmClipboard {
|
||||||
pub(crate) fn new(message_proxy: WasmClipboardMessageProxy, js_callbacks: JsClipboardCallbacks) -> Self {
|
pub(crate) fn new(message_proxy: WasmClipboardMessageProxy, js_callbacks: JsClipboardCallbacks) -> Self {
|
||||||
Self {
|
Self {
|
||||||
local_clipboard: None,
|
local_clipboard: None,
|
||||||
remote_clipboard: ClipboardTransaction::new(),
|
remote_clipboard: ClipboardTransaction::construct(),
|
||||||
proxy: message_proxy,
|
proxy: message_proxy,
|
||||||
js_callbacks,
|
js_callbacks,
|
||||||
|
|
||||||
|
@ -505,7 +505,7 @@ impl WasmClipboard {
|
||||||
} else {
|
} else {
|
||||||
// If no initial clipboard callback was set, send empty format list instead
|
// If no initial clipboard callback was set, send empty format list instead
|
||||||
return self.process_event(WasmClipboardBackendMessage::LocalClipboardChanged(
|
return self.process_event(WasmClipboardBackendMessage::LocalClipboardChanged(
|
||||||
ClipboardTransaction::new(),
|
ClipboardTransaction::construct(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl ClipboardTransaction {
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl ClipboardTransaction {
|
impl ClipboardTransaction {
|
||||||
pub fn new() -> Self {
|
pub fn construct() -> Self {
|
||||||
Self { contents: Vec::new() }
|
Self { contents: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum IronRdpErrorKind {
|
pub enum RemoteDesktopErrorKind {
|
||||||
/// Catch-all error kind
|
/// Catch-all error kind
|
||||||
General,
|
General,
|
||||||
/// Incorrect password used
|
/// Incorrect password used
|
||||||
|
@ -19,30 +19,30 @@ pub enum IronRdpErrorKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct IronRdpError {
|
pub struct RemoteDesktopError {
|
||||||
kind: IronRdpErrorKind,
|
kind: RemoteDesktopErrorKind,
|
||||||
source: anyhow::Error,
|
source: anyhow::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IronRdpError {
|
impl RemoteDesktopError {
|
||||||
pub fn with_kind(mut self, kind: IronRdpErrorKind) -> Self {
|
pub fn with_kind(mut self, kind: RemoteDesktopErrorKind) -> Self {
|
||||||
self.kind = kind;
|
self.kind = kind;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl IronRdpError {
|
impl RemoteDesktopError {
|
||||||
pub fn backtrace(&self) -> String {
|
pub fn backtrace(&self) -> String {
|
||||||
format!("{:?}", self.source)
|
format!("{:?}", self.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> IronRdpErrorKind {
|
pub fn kind(&self) -> RemoteDesktopErrorKind {
|
||||||
self.kind
|
self.kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<connector::ConnectorError> for IronRdpError {
|
impl From<connector::ConnectorError> for RemoteDesktopError {
|
||||||
fn from(e: connector::ConnectorError) -> Self {
|
fn from(e: connector::ConnectorError) -> Self {
|
||||||
use sspi::credssp::NStatusCode;
|
use sspi::credssp::NStatusCode;
|
||||||
|
|
||||||
|
@ -50,13 +50,13 @@ impl From<connector::ConnectorError> for IronRdpError {
|
||||||
ConnectorErrorKind::Credssp(sspi::Error {
|
ConnectorErrorKind::Credssp(sspi::Error {
|
||||||
nstatus: Some(NStatusCode::WRONG_PASSWORD),
|
nstatus: Some(NStatusCode::WRONG_PASSWORD),
|
||||||
..
|
..
|
||||||
}) => IronRdpErrorKind::WrongPassword,
|
}) => RemoteDesktopErrorKind::WrongPassword,
|
||||||
ConnectorErrorKind::Credssp(sspi::Error {
|
ConnectorErrorKind::Credssp(sspi::Error {
|
||||||
nstatus: Some(NStatusCode::LOGON_FAILURE),
|
nstatus: Some(NStatusCode::LOGON_FAILURE),
|
||||||
..
|
..
|
||||||
}) => IronRdpErrorKind::LogonFailure,
|
}) => RemoteDesktopErrorKind::LogonFailure,
|
||||||
ConnectorErrorKind::AccessDenied => IronRdpErrorKind::AccessDenied,
|
ConnectorErrorKind::AccessDenied => RemoteDesktopErrorKind::AccessDenied,
|
||||||
_ => IronRdpErrorKind::General,
|
_ => RemoteDesktopErrorKind::General,
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -66,19 +66,19 @@ impl From<connector::ConnectorError> for IronRdpError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ironrdp::session::SessionError> for IronRdpError {
|
impl From<ironrdp::session::SessionError> for RemoteDesktopError {
|
||||||
fn from(e: ironrdp::session::SessionError) -> Self {
|
fn from(e: ironrdp::session::SessionError) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: IronRdpErrorKind::General,
|
kind: RemoteDesktopErrorKind::General,
|
||||||
source: anyhow::Error::new(e),
|
source: anyhow::Error::new(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<anyhow::Error> for IronRdpError {
|
impl From<anyhow::Error> for RemoteDesktopError {
|
||||||
fn from(e: anyhow::Error) -> Self {
|
fn from(e: anyhow::Error) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: IronRdpErrorKind::General,
|
kind: RemoteDesktopErrorKind::General,
|
||||||
source: e,
|
source: e,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub struct DeviceEvent(pub(crate) Operation);
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl DeviceEvent {
|
impl DeviceEvent {
|
||||||
pub fn new_mouse_button_pressed(button: u8) -> Self {
|
pub fn mouse_button_pressed(button: u8) -> Self {
|
||||||
match MouseButton::from_web_button(button) {
|
match MouseButton::from_web_button(button) {
|
||||||
Some(button) => Self(Operation::MouseButtonPressed(button)),
|
Some(button) => Self(Operation::MouseButtonPressed(button)),
|
||||||
None => {
|
None => {
|
||||||
|
@ -18,7 +18,7 @@ impl DeviceEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_mouse_button_released(button: u8) -> Self {
|
pub fn mouse_button_released(button: u8) -> Self {
|
||||||
match MouseButton::from_web_button(button) {
|
match MouseButton::from_web_button(button) {
|
||||||
Some(button) => Self(Operation::MouseButtonReleased(button)),
|
Some(button) => Self(Operation::MouseButtonReleased(button)),
|
||||||
None => {
|
None => {
|
||||||
|
@ -28,30 +28,30 @@ impl DeviceEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_mouse_move(x: u16, y: u16) -> Self {
|
pub fn mouse_move(x: u16, y: u16) -> Self {
|
||||||
Self(Operation::MouseMove(MousePosition { x, y }))
|
Self(Operation::MouseMove(MousePosition { x, y }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_wheel_rotations(vertical: bool, rotation_units: i16) -> Self {
|
pub fn wheel_rotations(vertical: bool, rotation_units: i16) -> Self {
|
||||||
Self(Operation::WheelRotations(WheelRotations {
|
Self(Operation::WheelRotations(WheelRotations {
|
||||||
is_vertical: vertical,
|
is_vertical: vertical,
|
||||||
rotation_units,
|
rotation_units,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_key_pressed(scancode: u16) -> Self {
|
pub fn key_pressed(scancode: u16) -> Self {
|
||||||
Self(Operation::KeyPressed(Scancode::from_u16(scancode)))
|
Self(Operation::KeyPressed(Scancode::from_u16(scancode)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_key_released(scancode: u16) -> Self {
|
pub fn key_released(scancode: u16) -> Self {
|
||||||
Self(Operation::KeyReleased(Scancode::from_u16(scancode)))
|
Self(Operation::KeyReleased(Scancode::from_u16(scancode)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_unicode_pressed(unicode: char) -> Self {
|
pub fn unicode_pressed(unicode: char) -> Self {
|
||||||
Self(Operation::UnicodeKeyPressed(unicode))
|
Self(Operation::UnicodeKeyPressed(unicode))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_unicode_released(unicode: char) -> Self {
|
pub fn unicode_released(unicode: char) -> Self {
|
||||||
Self(Operation::UnicodeKeyReleased(unicode))
|
Self(Operation::UnicodeKeyReleased(unicode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ pub struct InputTransaction(pub(crate) SmallVec<[Operation; 3]>);
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl InputTransaction {
|
impl InputTransaction {
|
||||||
pub fn new() -> Self {
|
pub fn construct() -> Self {
|
||||||
Self(SmallVec::new())
|
Self(SmallVec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ mod session;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn ironrdp_init(log_level: &str) {
|
pub fn iron_init(log_level: &str) {
|
||||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
// `set_panic_hook` function at least once during initialization, and then
|
// `set_panic_hook` function at least once during initialization, and then
|
||||||
// we will get better error messages if our code ever panics.
|
// we will get better error messages if our code ever panics.
|
||||||
|
@ -69,7 +69,7 @@ pub struct DesktopSize {
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl DesktopSize {
|
impl DesktopSize {
|
||||||
pub fn new(width: u16, height: u16) -> Self {
|
pub fn construct(width: u16, height: u16) -> Self {
|
||||||
DesktopSize { width, height }
|
DesktopSize { width, height }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDiscon
|
||||||
use ironrdp_core::WriteBuf;
|
use ironrdp_core::WriteBuf;
|
||||||
use ironrdp_futures::{single_sequence_step_read, FramedWrite};
|
use ironrdp_futures::{single_sequence_step_read, FramedWrite};
|
||||||
use rgb::AsPixels as _;
|
use rgb::AsPixels as _;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tap::prelude::*;
|
use tap::prelude::*;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen_futures::spawn_local;
|
use wasm_bindgen_futures::spawn_local;
|
||||||
|
@ -36,7 +37,7 @@ use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
use crate::canvas::Canvas;
|
use crate::canvas::Canvas;
|
||||||
use crate::clipboard::{ClipboardTransaction, WasmClipboard, WasmClipboardBackend, WasmClipboardBackendMessage};
|
use crate::clipboard::{ClipboardTransaction, WasmClipboard, WasmClipboardBackend, WasmClipboardBackendMessage};
|
||||||
use crate::error::{IronRdpError, IronRdpErrorKind};
|
use crate::error::{RemoteDesktopError, RemoteDesktopErrorKind};
|
||||||
use crate::image::extract_partial_image;
|
use crate::image::extract_partial_image;
|
||||||
use crate::input::InputTransaction;
|
use crate::input::InputTransaction;
|
||||||
use crate::network_client::WasmNetworkClient;
|
use crate::network_client::WasmNetworkClient;
|
||||||
|
@ -102,7 +103,7 @@ impl Default for SessionBuilderInner {
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl SessionBuilder {
|
impl SessionBuilder {
|
||||||
pub fn new() -> SessionBuilder {
|
pub fn construct() -> SessionBuilder {
|
||||||
Self(Rc::new(RefCell::new(SessionBuilderInner::default())))
|
Self(Rc::new(RefCell::new(SessionBuilderInner::default())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,18 +147,6 @@ impl SessionBuilder {
|
||||||
self.clone()
|
self.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional
|
|
||||||
pub fn pcb(&self, pcb: String) -> SessionBuilder {
|
|
||||||
self.0.borrow_mut().pcb = Some(pcb);
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Optional
|
|
||||||
pub fn kdc_proxy_url(&self, kdc_proxy_url: Option<String>) -> SessionBuilder {
|
|
||||||
self.0.borrow_mut().kdc_proxy_url = kdc_proxy_url;
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Optional
|
/// Optional
|
||||||
pub fn desktop_size(&self, desktop_size: DesktopSize) -> SessionBuilder {
|
pub fn desktop_size(&self, desktop_size: DesktopSize) -> SessionBuilder {
|
||||||
self.0.borrow_mut().desktop_size = desktop_size;
|
self.0.borrow_mut().desktop_size = desktop_size;
|
||||||
|
@ -216,13 +205,22 @@ impl SessionBuilder {
|
||||||
self.clone()
|
self.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional
|
pub fn extension(&self, value: JsValue) -> SessionBuilder {
|
||||||
pub fn use_display_control(&self) -> SessionBuilder {
|
match serde_wasm_bindgen::from_value::<Extension>(value) {
|
||||||
self.0.borrow_mut().use_display_control = true;
|
Ok(value) => match value {
|
||||||
|
Extension::KdcProxyUrl(kdc_proxy_url) => self.0.borrow_mut().kdc_proxy_url = Some(kdc_proxy_url),
|
||||||
|
Extension::Pcb(pcb) => self.0.borrow_mut().pcb = Some(pcb),
|
||||||
|
Extension::DisplayControl(use_display_control) => {
|
||||||
|
self.0.borrow_mut().use_display_control = use_display_control
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => error!(%error, "Unsupported extension value"),
|
||||||
|
}
|
||||||
|
|
||||||
self.clone()
|
self.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect(&self) -> Result<Session, IronRdpError> {
|
pub async fn connect(&self) -> Result<Session, RemoteDesktopError> {
|
||||||
let (
|
let (
|
||||||
username,
|
username,
|
||||||
destination,
|
destination,
|
||||||
|
@ -297,11 +295,11 @@ impl SessionBuilder {
|
||||||
loop {
|
loop {
|
||||||
match ws.state() {
|
match ws.state() {
|
||||||
websocket::State::Closing | websocket::State::Closed => {
|
websocket::State::Closing | websocket::State::Closed => {
|
||||||
return Err(IronRdpError::from(anyhow::anyhow!(
|
return Err(RemoteDesktopError::from(anyhow::anyhow!(
|
||||||
"Failed to connect to {proxy_address} (WebSocket is `{:?}`)",
|
"failed to connect to {proxy_address} (WebSocket is `{:?}`)",
|
||||||
ws.state()
|
ws.state()
|
||||||
))
|
))
|
||||||
.with_kind(IronRdpErrorKind::ProxyConnect));
|
.with_kind(RemoteDesktopErrorKind::ProxyConnect));
|
||||||
}
|
}
|
||||||
websocket::State::Connecting => {
|
websocket::State::Connecting => {
|
||||||
trace!("WebSocket is connecting to proxy at {proxy_address}...");
|
trace!("WebSocket is connecting to proxy at {proxy_address}...");
|
||||||
|
@ -354,6 +352,13 @@ impl SessionBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
enum Extension {
|
||||||
|
KdcProxyUrl(String),
|
||||||
|
Pcb(String),
|
||||||
|
DisplayControl(bool),
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) type FastPathInputEvents = smallvec::SmallVec<[FastPathInputEvent; 2]>;
|
pub(crate) type FastPathInputEvents = smallvec::SmallVec<[FastPathInputEvent; 2]>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -412,7 +417,7 @@ pub struct Session {
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Session {
|
impl Session {
|
||||||
pub async fn run(&self) -> Result<SessionTerminationInfo, IronRdpError> {
|
pub async fn run(&self) -> Result<SessionTerminationInfo, RemoteDesktopError> {
|
||||||
let rdp_reader = self
|
let rdp_reader = self
|
||||||
.rdp_reader
|
.rdp_reader
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
@ -707,17 +712,17 @@ impl Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_inputs(&self, transaction: InputTransaction) -> Result<(), IronRdpError> {
|
pub fn apply_inputs(&self, transaction: InputTransaction) -> Result<(), RemoteDesktopError> {
|
||||||
let inputs = self.input_database.borrow_mut().apply(transaction);
|
let inputs = self.input_database.borrow_mut().apply(transaction);
|
||||||
self.h_send_inputs(inputs)
|
self.h_send_inputs(inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn release_all_inputs(&self) -> Result<(), IronRdpError> {
|
pub fn release_all_inputs(&self) -> Result<(), RemoteDesktopError> {
|
||||||
let inputs = self.input_database.borrow_mut().release_all();
|
let inputs = self.input_database.borrow_mut().release_all();
|
||||||
self.h_send_inputs(inputs)
|
self.h_send_inputs(inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn h_send_inputs(&self, inputs: smallvec::SmallVec<[FastPathInputEvent; 2]>) -> Result<(), IronRdpError> {
|
fn h_send_inputs(&self, inputs: smallvec::SmallVec<[FastPathInputEvent; 2]>) -> Result<(), RemoteDesktopError> {
|
||||||
if !inputs.is_empty() {
|
if !inputs.is_empty() {
|
||||||
trace!("Inputs: {inputs:?}");
|
trace!("Inputs: {inputs:?}");
|
||||||
|
|
||||||
|
@ -735,7 +740,7 @@ impl Session {
|
||||||
num_lock: bool,
|
num_lock: bool,
|
||||||
caps_lock: bool,
|
caps_lock: bool,
|
||||||
kana_lock: bool,
|
kana_lock: bool,
|
||||||
) -> Result<(), IronRdpError> {
|
) -> Result<(), RemoteDesktopError> {
|
||||||
use ironrdp::pdu::input::fast_path::FastPathInput;
|
use ironrdp::pdu::input::fast_path::FastPathInput;
|
||||||
|
|
||||||
let event = ironrdp::input::synchronize_event(scroll_lock, num_lock, caps_lock, kana_lock);
|
let event = ironrdp::input::synchronize_event(scroll_lock, num_lock, caps_lock, kana_lock);
|
||||||
|
@ -750,7 +755,7 @@ impl Session {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shutdown(&self) -> Result<(), IronRdpError> {
|
pub fn shutdown(&self) -> Result<(), RemoteDesktopError> {
|
||||||
self.input_events_tx
|
self.input_events_tx
|
||||||
.unbounded_send(RdpInputEvent::TerminateSession)
|
.unbounded_send(RdpInputEvent::TerminateSession)
|
||||||
.context("failed to send terminate session event to writer task")?;
|
.context("failed to send terminate session event to writer task")?;
|
||||||
|
@ -758,7 +763,7 @@ impl Session {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn on_clipboard_paste(&self, content: ClipboardTransaction) -> Result<(), IronRdpError> {
|
pub async fn on_clipboard_paste(&self, content: ClipboardTransaction) -> Result<(), RemoteDesktopError> {
|
||||||
self.input_events_tx
|
self.input_events_tx
|
||||||
.unbounded_send(RdpInputEvent::ClipboardBackend(
|
.unbounded_send(RdpInputEvent::ClipboardBackend(
|
||||||
WasmClipboardBackendMessage::LocalClipboardChanged(content),
|
WasmClipboardBackendMessage::LocalClipboardChanged(content),
|
||||||
|
@ -768,7 +773,7 @@ impl Session {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_cursor_style(&self, style: CursorStyle) -> Result<(), IronRdpError> {
|
fn set_cursor_style(&self, style: CursorStyle) -> Result<(), RemoteDesktopError> {
|
||||||
let (kind, data, hotspot_x, hotspot_y) = match style {
|
let (kind, data, hotspot_x, hotspot_y) = match style {
|
||||||
CursorStyle::Default => ("default", None, None, None),
|
CursorStyle::Default => ("default", None, None, None),
|
||||||
CursorStyle::Hidden => ("hidden", None, None, None),
|
CursorStyle::Hidden => ("hidden", None, None, None),
|
||||||
|
@ -818,6 +823,10 @@ impl Session {
|
||||||
// plain scancode events are allowed to function correctly).
|
// plain scancode events are allowed to function correctly).
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extension_call(_value: JsValue) -> Result<JsValue, RemoteDesktopError> {
|
||||||
|
Ok(JsValue::null())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_config(
|
fn build_config(
|
||||||
|
@ -913,7 +922,7 @@ async fn connect(
|
||||||
clipboard_backend,
|
clipboard_backend,
|
||||||
use_display_control,
|
use_display_control,
|
||||||
}: ConnectParams,
|
}: ConnectParams,
|
||||||
) -> Result<(connector::ConnectionResult, WebSocket), IronRdpError> {
|
) -> Result<(connector::ConnectionResult, WebSocket), RemoteDesktopError> {
|
||||||
let mut framed = ironrdp_futures::LocalFuturesFramed::new(ws);
|
let mut framed = ironrdp_futures::LocalFuturesFramed::new(ws);
|
||||||
|
|
||||||
let mut connector = ClientConnector::new(config);
|
let mut connector = ClientConnector::new(config);
|
||||||
|
@ -960,7 +969,7 @@ async fn connect_rdcleanpath<S>(
|
||||||
destination: String,
|
destination: String,
|
||||||
proxy_auth_token: String,
|
proxy_auth_token: String,
|
||||||
pcb: Option<String>,
|
pcb: Option<String>,
|
||||||
) -> Result<(ironrdp_futures::Upgraded, Vec<u8>), IronRdpError>
|
) -> Result<(ironrdp_futures::Upgraded, Vec<u8>), RemoteDesktopError>
|
||||||
where
|
where
|
||||||
S: ironrdp_futures::FramedRead + FramedWrite,
|
S: ironrdp_futures::FramedRead + FramedWrite,
|
||||||
{
|
{
|
||||||
|
@ -1039,10 +1048,10 @@ where
|
||||||
server_addr,
|
server_addr,
|
||||||
} => (x224_connection_response, server_cert_chain, server_addr),
|
} => (x224_connection_response, server_cert_chain, server_addr),
|
||||||
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
||||||
return Err(
|
return Err(RemoteDesktopError::from(
|
||||||
IronRdpError::from(anyhow::anyhow!("received an RDCleanPath error: {error}"))
|
anyhow::Error::new(error).context("received an RDCleanPath error"),
|
||||||
.with_kind(IronRdpErrorKind::RDCleanPath),
|
)
|
||||||
);
|
.with_kind(RemoteDesktopErrorKind::RDCleanPath));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
IronRDP also supports the web browser as a first class target.
|
IronRDP also supports the web browser as a first class target.
|
||||||
|
|
||||||
See the [iron-remote-gui](./iron-remote-gui) for the reusable Web Component, and [iron-svelte-client](./iron-svelte-client) for a demonstration.
|
See the [iron-remote-desktop](./iron-remote-desktop) for the reusable Web Component, and [iron-svelte-client](./iron-svelte-client) for a demonstration.
|
||||||
|
|
||||||
Note that the demonstration client is not intended to be used in production as-is.
|
Note that the demonstration client is not intended to be used in production as-is.
|
||||||
Devolutions is shipping well-integrated, production-ready IronRDP web clients as part of:
|
Devolutions is shipping well-integrated, production-ready IronRDP web clients as part of:
|
||||||
|
|
15
web-client/iron-remote-desktop-rdp/.prettierignore
Normal file
15
web-client/iron-remote-desktop-rdp/.prettierignore
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
node_modules/
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
/package
|
||||||
|
/build
|
||||||
|
/static/bearcss
|
||||||
|
/static/material-icons
|
||||||
|
/dist
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
19
web-client/iron-remote-desktop-rdp/.prettierrc.yaml
Normal file
19
web-client/iron-remote-desktop-rdp/.prettierrc.yaml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Prettier:
|
||||||
|
# - https://prettier.io/docs/en/options
|
||||||
|
---
|
||||||
|
useTabs: false
|
||||||
|
tabWidth: 4
|
||||||
|
singleQuote: true
|
||||||
|
semi: true
|
||||||
|
trailingComma: all
|
||||||
|
printWidth: 120
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
- files:
|
||||||
|
- '*.yml'
|
||||||
|
- '*.yaml'
|
||||||
|
- '*.json'
|
||||||
|
- '*.html'
|
||||||
|
- '*.md'
|
||||||
|
options:
|
||||||
|
tabWidth: 2
|
23
web-client/iron-remote-desktop-rdp/README.md
Normal file
23
web-client/iron-remote-desktop-rdp/README.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Iron Remote Desktop RDP
|
||||||
|
|
||||||
|
This is implementation of `RemoteDesktopModule` interface from [iron-remote-desktop](../iron-remote-desktop) for RDP connection.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Make your modification in the source code then use [iron-svelte-client](../iron-svelte-client) to test.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `npm run build`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
As member of the Devolutions organization, you can import the Web Component from JFrog Artifactory by running the following npm command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ npm install @devolutions/iron-remote-desktop-rdp
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise, you can run `npm install` targeting the `dist/` folder directly.
|
||||||
|
|
||||||
|
Import the `iron-remote-desktop-rdp.umd.cjs` from `node_modules/` folder.
|
80
web-client/iron-remote-desktop-rdp/eslint.config.mjs
Normal file
80
web-client/iron-remote-desktop-rdp/eslint.config.mjs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import { FlatCompat } from '@eslint/eslintrc';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'**/*.cjs',
|
||||||
|
'**/.DS_Store',
|
||||||
|
'**/node_modules',
|
||||||
|
'build',
|
||||||
|
'package',
|
||||||
|
'**/.env',
|
||||||
|
'**/.env.*',
|
||||||
|
'!**/.env.example',
|
||||||
|
'**/pnpm-lock.yaml',
|
||||||
|
'**/package-lock.json',
|
||||||
|
'**/yarn.lock',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'),
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': typescriptEslint,
|
||||||
|
},
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: tsParser,
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
project: './tsconfig.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
strict: 2,
|
||||||
|
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'@typescript-eslint/strict-boolean-expressions': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
allowString: false,
|
||||||
|
allowNumber: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
endOfLine: 'auto',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
3995
web-client/iron-remote-desktop-rdp/package-lock.json
generated
Normal file
3995
web-client/iron-remote-desktop-rdp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
51
web-client/iron-remote-desktop-rdp/package.json
Normal file
51
web-client/iron-remote-desktop-rdp/package.json
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"name": "@devolutions/iron-remote-desktop-rdp",
|
||||||
|
"author": "Nicolas Girot",
|
||||||
|
"email": "ngirot@devolutions.net",
|
||||||
|
"contributors": [
|
||||||
|
"Benoit Cortier",
|
||||||
|
"Irving Ou",
|
||||||
|
"Vladislav Nikonov",
|
||||||
|
"Zacharia Ellaham",
|
||||||
|
"Alexandr Yusuk"
|
||||||
|
],
|
||||||
|
"description": "Web Component providing agnostic implementation for Iron Wasm base client",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npm run pre-build && vite",
|
||||||
|
"build": "npm run pre-build && vite build",
|
||||||
|
"build-alone": "vite build",
|
||||||
|
"pre-build": "node ./pre-build.js",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "tsc --noEmit",
|
||||||
|
"check:dist": "tsc ./dist/index.d.ts --noEmit",
|
||||||
|
"check:watch": "tsc --watch --noEmit",
|
||||||
|
"lint": "npm run lint:prettier && npm run lint:eslint",
|
||||||
|
"lint:prettier": "prettier --check .",
|
||||||
|
"lint:eslint": "eslint src/**",
|
||||||
|
"format": "prettier --write ."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.3.0",
|
||||||
|
"@eslint/js": "^9.21.0",
|
||||||
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
||||||
|
"eslint": "^9.21.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
|
"globals": "^16.0.0",
|
||||||
|
"prettier": "^3.1.0",
|
||||||
|
"tslib": "^2.4.1",
|
||||||
|
"typescript": "~5.7.2",
|
||||||
|
"vite": "^6.2.0",
|
||||||
|
"vite-plugin-dts": "^4.5.0",
|
||||||
|
"vite-plugin-top-level-await": "^1.2.2",
|
||||||
|
"vite-plugin-wasm": "^3.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"rxjs": "^6.6.7",
|
||||||
|
"ua-parser-js": "^1.0.33"
|
||||||
|
}
|
||||||
|
}
|
34
web-client/iron-remote-desktop-rdp/pre-build.js
Normal file
34
web-client/iron-remote-desktop-rdp/pre-build.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
|
const run = async (command, cwd) => {
|
||||||
|
try {
|
||||||
|
const buildCommand = spawn(command, {
|
||||||
|
stdio: 'pipe',
|
||||||
|
shell: true,
|
||||||
|
cwd: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
buildCommand.stdout.on('data', (data) => {
|
||||||
|
console.log(`${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
buildCommand.stderr.on('data', (data) => {
|
||||||
|
console.error(`${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const exitCode = await new Promise((resolve, reject) => {
|
||||||
|
buildCommand.on('close', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
reject(new Error(`Process exited with non-zero code: ${code}`));
|
||||||
|
}
|
||||||
|
resolve(code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Child process exited with code: ${exitCode}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Process run failed: ${err}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await run('cargo xtask web build', '../../');
|
22
web-client/iron-remote-desktop-rdp/public/package.json
Normal file
22
web-client/iron-remote-desktop-rdp/public/package.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "@devolutions/iron-remote-desktop-rdp",
|
||||||
|
"author": "Nicolas Girot",
|
||||||
|
"email": "ngirot@devolutions.net",
|
||||||
|
"contributors": [
|
||||||
|
"Benoit Cortier",
|
||||||
|
"Irving Ou",
|
||||||
|
"Vladislav Nikonov",
|
||||||
|
"Zacharia Ellaham"
|
||||||
|
],
|
||||||
|
"description": "Web Component providing agnostic implementation for Iron Wasm base client.",
|
||||||
|
"version": "0.13.1",
|
||||||
|
"main": "iron-remote-desktop-rdp.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"iron-remote-desktop-rdp.js",
|
||||||
|
"index.d.ts"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"rxjs": "^6.6.7"
|
||||||
|
}
|
||||||
|
}
|
26
web-client/iron-remote-desktop-rdp/src/main.ts
Normal file
26
web-client/iron-remote-desktop-rdp/src/main.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import init, {
|
||||||
|
iron_init,
|
||||||
|
DesktopSize,
|
||||||
|
DeviceEvent,
|
||||||
|
InputTransaction,
|
||||||
|
RemoteDesktopError,
|
||||||
|
Session,
|
||||||
|
SessionBuilder,
|
||||||
|
SessionTerminationInfo,
|
||||||
|
ClipboardTransaction,
|
||||||
|
ClipboardContent,
|
||||||
|
} from '../../../crates/ironrdp-web/pkg/ironrdp_web';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
init,
|
||||||
|
iron_init,
|
||||||
|
DesktopSize,
|
||||||
|
DeviceEvent,
|
||||||
|
InputTransaction,
|
||||||
|
RemoteDesktopError,
|
||||||
|
SessionBuilder,
|
||||||
|
ClipboardTransaction,
|
||||||
|
ClipboardContent,
|
||||||
|
Session,
|
||||||
|
SessionTerminationInfo,
|
||||||
|
};
|
1
web-client/iron-remote-desktop-rdp/src/vite-env.d.ts
vendored
Normal file
1
web-client/iron-remote-desktop-rdp/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
30
web-client/iron-remote-desktop-rdp/tsconfig.json
Normal file
30
web-client/iron-remote-desktop-rdp/tsconfig.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
/**
|
||||||
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
|
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||||
|
* Note that setting allowJs false does not prevent the use
|
||||||
|
* of JS in `.svelte` files.
|
||||||
|
*/
|
||||||
|
"allowJs": false,
|
||||||
|
"checkJs": false,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.js"],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
25
web-client/iron-remote-desktop-rdp/vite.config.ts
Normal file
25
web-client/iron-remote-desktop-rdp/vite.config.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||||
|
import dtsPlugin from 'vite-plugin-dts';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: './src/main.ts',
|
||||||
|
name: 'IronRemoteDesktopRdp',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
strict: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
topLevelAwait(),
|
||||||
|
dtsPlugin({
|
||||||
|
rollupTypes: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
27
web-client/iron-remote-desktop/.gitignore
vendored
Normal file
27
web-client/iron-remote-desktop/.gitignore
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
1
web-client/iron-remote-desktop/.npmrc
Normal file
1
web-client/iron-remote-desktop/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
|
@ -1,16 +1,16 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
/.svelte-kit
|
/.svelte-kit
|
||||||
/package
|
/package
|
||||||
/build
|
/build
|
||||||
/static/bearcss
|
/static/bearcss
|
||||||
/static/material-icons
|
/static/material-icons
|
||||||
/dist
|
/dist
|
||||||
# Ignore files for PNPM, NPM and YARN
|
# Ignore files for PNPM, NPM and YARN
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
|
@ -1,6 +1,7 @@
|
||||||
# Iron Remote GUI
|
# Iron Remote Desktop
|
||||||
|
|
||||||
This is the core of the web client written on top of Svelte and built as a reusable Web Component.
|
This is the core of the web client written on top of Svelte and built as a reusable Web Component.
|
||||||
|
Also, it contains the TypeScript interfaces exposed by WebAssembly bindings from `ironrdp-web` and used by `iron-svelte-client`.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
@ -15,17 +16,17 @@ Run `npm run build`
|
||||||
As member of the Devolutions organization, you can import the Web Component from JFrog Artifactory by running the following npm command:
|
As member of the Devolutions organization, you can import the Web Component from JFrog Artifactory by running the following npm command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ npm install @devolutions/iron-remote-gui
|
$ npm install @devolutions/iron-remote-desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
Otherwise, you can run `npm install` targeting the `dist/` folder directly.
|
Otherwise, you can run `npm install` targeting the `dist/` folder directly.
|
||||||
|
|
||||||
Import the `iron-remote-gui.umd.cjs` from `node_modules/` folder.
|
Import the `iron-remote-desktop.umd.cjs` from `node_modules/` folder.
|
||||||
|
|
||||||
Then use the HTML tag `<iron-remote-gui/>` in your page.
|
Then use the HTML tag `<iron-remote-desktop/>` in your page.
|
||||||
|
|
||||||
In your code add a listener for the `ready` event on the `iron-remote-gui` HTML element.
|
In your code add a listener for the `ready` event on the `iron-remote-desktop` HTML element.
|
||||||
Get `evt.detail.irgUserInteraction` from the `Promise`, a property whose type is `UserInteractionService`.
|
Get `evt.detail.irgUserInteraction` from the `Promise`, a property whose type is `UserInteraction`.
|
||||||
Call the `connect` method on this object.
|
Call the `connect` method on this object.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
@ -35,22 +36,23 @@ You need to recreate them on your application for now (it will be improved in fu
|
||||||
|
|
||||||
Also, even if the connection to RDP work there is still a lot of improvement to do.
|
Also, even if the connection to RDP work there is still a lot of improvement to do.
|
||||||
As of now, you can expect, mouse movement and click (4 buttons) - no scroll, Keyboard for at least the standard.
|
As of now, you can expect, mouse movement and click (4 buttons) - no scroll, Keyboard for at least the standard.
|
||||||
Windows and CTRL+ALT+DEL can be called by method on `UserInteractionService`.
|
Windows and CTRL+ALT+DEL can be called by method on `UserInteraction`.
|
||||||
Lock keys (like caps lock), have a partial support.
|
Lock keys (like caps lock), have a partial support.
|
||||||
Other advanced functionalities (sharing / copy past...) are not implemented yet.
|
Other advanced functionalities (sharing / copy past...) are not implemented yet.
|
||||||
|
|
||||||
## Component parameters
|
## Component parameters
|
||||||
|
|
||||||
You can add some parameters for default initialization on the component `<iron-remote-gui />`.
|
You can add some parameters for default initialization on the component `<iron-remote-desktop />`.
|
||||||
|
|
||||||
> Note that due to a limitation of the framework all parameters need to be lower-cased.
|
> Note that due to a limitation of the framework all parameters need to be lower-cased.
|
||||||
|
|
||||||
- `scale`: The scaling behavior of the distant screen. Can be `fit`, `real` or `full`. Default is `real`;
|
- `scale`: The scaling behavior of the distant screen. Can be `fit`, `real` or `full`. Default is `real`;
|
||||||
- `verbose`: Show logs from `iron-remote-gui`. `true` or `false`. Default is `false`.
|
- `verbose`: Show logs from `iron-remote-desktop`. `true` or `false`. Default is `false`.
|
||||||
- `debugwasm`: Show debug info from web assembly. Can be `"OFF"`, `"ERROR"`, `"WARN"`, `"INFO"`, `"DEBUG"`, `"TRACE"`. Default is `"OFF"`.
|
- `debugwasm`: Show debug info from web assembly. Can be `"OFF"`, `"ERROR"`, `"WARN"`, `"INFO"`, `"DEBUG"`, `"TRACE"`. Default is `"OFF"`.
|
||||||
- `flexcentre`: Helper to force `iron-remote-gui` a flex and centering the content automatically. Otherwise, you need to manage manually. Default is `true`.
|
- `flexcentre`: Helper to force `iron-remote-desktop` a flex and centering the content automatically. Otherwise, you need to manage manually. Default is `true`.
|
||||||
|
- `module`: An implementation of the [RemoteDesktopModule](./src/interfaces/RemoteDesktopModule.ts)
|
||||||
|
|
||||||
## `UserInteractionService` methods
|
## `UserInteraction` methods
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
connect(
|
connect(
|
||||||
|
@ -63,6 +65,7 @@ connect(
|
||||||
desktopSize?: DesktopSize,
|
desktopSize?: DesktopSize,
|
||||||
preConnectionBlob?: string,
|
preConnectionBlob?: string,
|
||||||
kdc_proxy_url?: string,
|
kdc_proxy_url?: string,
|
||||||
|
use_display_control: boolean,
|
||||||
): Observable<NewSessionInfo>;
|
): Observable<NewSessionInfo>;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -70,12 +73,20 @@ connect(
|
||||||
|
|
||||||
> `destination` refers to the Devolutions Gateway hostname and port.
|
> `destination` refers to the Devolutions Gateway hostname and port.
|
||||||
|
|
||||||
> `authtoken` is the authentication token to send to the Devolutions Gateway.
|
> `proxyAddress` is the address of the Devolutions Gateway proxy
|
||||||
|
|
||||||
> `serverDomain` is the Windows domain name (if the target computer has one)
|
> `serverDomain` is the Windows domain name (if the target computer has one)
|
||||||
|
|
||||||
|
> `authtoken` is the authentication token to send to the Devolutions Gateway.
|
||||||
|
|
||||||
|
> `desktopSize` is the initial size of the desktop
|
||||||
|
|
||||||
|
> `preConnectionBlob` is the pre connection blob data
|
||||||
|
|
||||||
> `kdc_proxy_url` is the URL to a KDC Proxy, as specified in [MS-KKDCP documentation](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kkdcp/5bcebb8d-b747-4ee5-9453-428aec1c5c38)
|
> `kdc_proxy_url` is the URL to a KDC Proxy, as specified in [MS-KKDCP documentation](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kkdcp/5bcebb8d-b747-4ee5-9453-428aec1c5c38)
|
||||||
|
|
||||||
|
> `use_display_control` is the value that defined if the Display Control Virtual Channel will be used.
|
||||||
|
|
||||||
> `ctrlAltDel()`
|
> `ctrlAltDel()`
|
||||||
>
|
>
|
||||||
> Sends the ctrl+alt+del key to server.
|
> Sends the ctrl+alt+del key to server.
|
||||||
|
@ -84,11 +95,31 @@ connect(
|
||||||
>
|
>
|
||||||
> Sends the meta key event to remote host (i.e.: Windows key).
|
> Sends the meta key event to remote host (i.e.: Windows key).
|
||||||
|
|
||||||
> `setVisibility(value: bool)`
|
> `setVisibility(value: boolean)`
|
||||||
>
|
>
|
||||||
> Shows or hides rendering canvas.
|
> Shows or hides rendering canvas.
|
||||||
|
|
||||||
> `setScale(scale: ScreenScale)`
|
> `setScale(scale: ScreenScale)`
|
||||||
>
|
>
|
||||||
> Sets the scale behavior of the canvas.
|
> Sets the scale behavior of the canvas.
|
||||||
> See the [ScreenScale](./src/services/user-interaction-service.ts) enum for possible values.
|
> See the [ScreenScale](./src/enums/ScreenScale.ts) enum for possible values.
|
||||||
|
|
||||||
|
> `shutdown()`
|
||||||
|
>
|
||||||
|
> Shutdowns the active session.
|
||||||
|
|
||||||
|
> `setKeyboardUnicodeMode(use_unicode: boolean)`
|
||||||
|
>
|
||||||
|
> Sets the keyboard Unicode mode.
|
||||||
|
|
||||||
|
> `setCursorStyleOverride(style?: string)`
|
||||||
|
>
|
||||||
|
> Overrides the default cursor style. If `style` is `null`, the default cursor style will be used.
|
||||||
|
|
||||||
|
> `resize(width: number, height: number, scale?: number)`
|
||||||
|
>
|
||||||
|
> Resizes the screen.
|
||||||
|
|
||||||
|
> `setEnableClipboard(enable: boolean)`
|
||||||
|
>
|
||||||
|
> Enables or disable the clipboard based on the `enable` value.
|
|
@ -7,10 +7,10 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
<iron-remote-gui isvisible="false" desktopwidth="600" desktopheight="400" />
|
<iron-remote-desktop isvisible="false" desktopwidth="600" desktopheight="400" />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var el = document.querySelector('iron-remote-gui');
|
var el = document.querySelector('iron-remote-desktop');
|
||||||
el.addEventListener('ready', (e) => {
|
el.addEventListener('ready', (e) => {
|
||||||
console.log('WebComponent Loaded');
|
console.log('WebComponent Loaded');
|
||||||
e.detail.irgUserInteraction.setVisibility(true);
|
e.detail.irgUserInteraction.setVisibility(true);
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "@devolutions/iron-remote-gui",
|
"name": "@devolutions/iron-remote-desktop",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@devolutions/iron-remote-gui",
|
"name": "@devolutions/iron-remote-desktop",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rxjs": "^6.6.7",
|
"rxjs": "^6.6.7",
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@devolutions/iron-remote-gui",
|
"name": "@devolutions/iron-remote-desktop",
|
||||||
"author": "Nicolas Girot",
|
"author": "Nicolas Girot",
|
||||||
"email": "ngirot@devolutions.net",
|
"email": "ngirot@devolutions.net",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
|
@ -13,10 +13,9 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run pre-build && vite",
|
"dev": "npm run vite",
|
||||||
"build": "npm run pre-build && vite build",
|
"build": "npm run vite build",
|
||||||
"build-alone": "vite build",
|
"build-alone": "vite build",
|
||||||
"pre-build": "node ./pre-build.js",
|
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:dist": "tsc ./dist/index.d.ts --noEmit",
|
"check:dist": "tsc ./dist/index.d.ts --noEmit",
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@devolutions/iron-remote-gui",
|
"name": "@devolutions/iron-remote-desktop",
|
||||||
"author": "Nicolas Girot",
|
"author": "Nicolas Girot",
|
||||||
"email": "ngirot@devolutions.net",
|
"email": "ngirot@devolutions.net",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
|
@ -10,10 +10,10 @@
|
||||||
],
|
],
|
||||||
"description": "Web Component providing agnostic implementation for Iron Wasm base client.",
|
"description": "Web Component providing agnostic implementation for Iron Wasm base client.",
|
||||||
"version": "0.13.1",
|
"version": "0.13.1",
|
||||||
"main": "iron-remote-gui.js",
|
"main": "iron-remote-desktop.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"iron-remote-gui.js",
|
"iron-remote-desktop.js",
|
||||||
"index.d.ts"
|
"index.d.ts"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
|
@ -6,11 +6,11 @@
|
||||||
<title>Test</title>
|
<title>Test</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="module" src="./iron-remote-gui.js"></script>
|
<script type="module" src="./iron-remote-desktop.js"></script>
|
||||||
<iron-remote-gui isvisible="false" desktopwidth="600" desktopheight="400" />
|
<iron-remote-desktop isvisible="false" desktopwidth="600" desktopheight="400" />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var el = document.querySelector('iron-remote-gui');
|
var el = document.querySelector('iron-remote-desktop');
|
||||||
el.addEventListener('ready', (e) => {
|
el.addEventListener('ready', (e) => {
|
||||||
e.detail.irgUserInteraction.setVisibility(true);
|
e.detail.irgUserInteraction.setVisibility(true);
|
||||||
});
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface ClipboardContent {
|
||||||
|
new_text(mime_type: string, text: string): ClipboardContent;
|
||||||
|
new_binary(mime_type: string, binary: Uint8Array): ClipboardContent;
|
||||||
|
mime_type(): string;
|
||||||
|
value(): string;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import type { ClipboardContent } from './ClipboardContent';
|
||||||
|
|
||||||
|
export interface ClipboardTransaction {
|
||||||
|
construct(): ClipboardTransaction;
|
||||||
|
add_content(content: ClipboardContent): void;
|
||||||
|
is_empty(): boolean;
|
||||||
|
content(): Array<ClipboardContent>;
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
export interface DesktopSize {
|
export interface DesktopSize {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
|
||||||
|
construct(width: number, height: number): DesktopSize;
|
||||||
}
|
}
|
10
web-client/iron-remote-desktop/src/interfaces/DeviceEvent.ts
Normal file
10
web-client/iron-remote-desktop/src/interfaces/DeviceEvent.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export interface DeviceEvent {
|
||||||
|
mouse_button_pressed(button: number): DeviceEvent;
|
||||||
|
mouse_button_released(button: number): DeviceEvent;
|
||||||
|
mouse_move(x: number, y: number): DeviceEvent;
|
||||||
|
wheel_rotations(vertical: boolean, rotation_units: number): DeviceEvent;
|
||||||
|
key_pressed(scancode: number): DeviceEvent;
|
||||||
|
key_released(scancode: number): DeviceEvent;
|
||||||
|
unicode_pressed(unicode: string): DeviceEvent;
|
||||||
|
unicode_released(unicode: string): DeviceEvent;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import type { DeviceEvent } from './DeviceEvent';
|
||||||
|
|
||||||
|
export interface InputTransaction {
|
||||||
|
construct(): InputTransaction;
|
||||||
|
add_event(event: DeviceEvent): void;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { DesktopSize } from './DesktopSize';
|
||||||
|
import type { DeviceEvent } from './DeviceEvent';
|
||||||
|
import type { InputTransaction } from './InputTransaction';
|
||||||
|
import type { RemoteDesktopError } from './session-event';
|
||||||
|
import type { Session } from './Session';
|
||||||
|
import type { SessionBuilder } from './SessionBuilder';
|
||||||
|
import type { SessionTerminationInfo } from './SessionTerminationInfo';
|
||||||
|
import type { ClipboardTransaction } from './ClipboardTransaction';
|
||||||
|
import type { ClipboardContent } from './ClipboardContent';
|
||||||
|
|
||||||
|
export interface RemoteDesktopModule {
|
||||||
|
init: () => Promise<unknown>;
|
||||||
|
iron_init: (logLevel: string) => void;
|
||||||
|
DesktopSize: DesktopSize;
|
||||||
|
DeviceEvent: DeviceEvent;
|
||||||
|
InputTransaction: InputTransaction;
|
||||||
|
RemoteDesktopError: RemoteDesktopError;
|
||||||
|
Session: Session;
|
||||||
|
SessionBuilder: SessionBuilder;
|
||||||
|
SessionTerminationInfo: SessionTerminationInfo;
|
||||||
|
ClipboardTransaction: ClipboardTransaction;
|
||||||
|
ClipboardContent: ClipboardContent;
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
export interface ServerRect {
|
export interface ServerRect {
|
||||||
free(): void;
|
|
||||||
|
|
||||||
clone_buffer(): Uint8Array;
|
|
||||||
|
|
||||||
bottom: number;
|
bottom: number;
|
||||||
left: number;
|
left: number;
|
||||||
right: number;
|
right: number;
|
||||||
top: number;
|
top: number;
|
||||||
|
|
||||||
|
clone_buffer(): Uint8Array;
|
||||||
}
|
}
|
23
web-client/iron-remote-desktop/src/interfaces/Session.ts
Normal file
23
web-client/iron-remote-desktop/src/interfaces/Session.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import type { InputTransaction } from './InputTransaction';
|
||||||
|
import type { DesktopSize } from './DesktopSize';
|
||||||
|
import type { SessionTerminationInfo } from './SessionTerminationInfo';
|
||||||
|
import type { ClipboardTransaction } from './ClipboardTransaction';
|
||||||
|
|
||||||
|
export interface Session {
|
||||||
|
run(): Promise<SessionTerminationInfo>;
|
||||||
|
desktop_size(): DesktopSize;
|
||||||
|
apply_inputs(transaction: InputTransaction): void;
|
||||||
|
release_all_inputs(): void;
|
||||||
|
synchronize_lock_keys(scroll_lock: boolean, num_lock: boolean, caps_lock: boolean, kana_lock: boolean): void;
|
||||||
|
extension_call(value: unknown): unknown;
|
||||||
|
shutdown(): void;
|
||||||
|
on_clipboard_paste(content: ClipboardTransaction): Promise<void>;
|
||||||
|
resize(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
scale_factor?: number | null,
|
||||||
|
physical_width?: number | null,
|
||||||
|
physical_height?: number | null,
|
||||||
|
): void;
|
||||||
|
supports_unicode_keyboard_shortcuts(): boolean;
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
import type { Session } from './Session';
|
||||||
|
import type { DesktopSize } from './DesktopSize';
|
||||||
|
import type { ClipboardTransaction } from './ClipboardTransaction';
|
||||||
|
|
||||||
|
export interface SessionBuilder {
|
||||||
|
construct(): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Required
|
||||||
|
*/
|
||||||
|
username(username: string): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Required
|
||||||
|
*/
|
||||||
|
destination(destination: string): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Optional
|
||||||
|
*/
|
||||||
|
server_domain(server_domain: string): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Required
|
||||||
|
*/
|
||||||
|
password(password: string): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Required
|
||||||
|
*/
|
||||||
|
proxy_address(address: string): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Required
|
||||||
|
*/
|
||||||
|
auth_token(token: string): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Optional
|
||||||
|
*/
|
||||||
|
desktop_size(desktop_size: DesktopSize): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Optional
|
||||||
|
*/
|
||||||
|
render_canvas(canvas: HTMLCanvasElement): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Required.
|
||||||
|
*
|
||||||
|
* # Cursor kinds:
|
||||||
|
* - `default` (default system cursor); other arguments are `UNDEFINED`
|
||||||
|
* - `none` (hide cursor); other arguments are `UNDEFINED`
|
||||||
|
* - `url` (custom cursor data URL); `cursor_data` contains the data URL with Base64-encoded
|
||||||
|
* cursor bitmap; `hotspot_x` and `hotspot_y` are set to the cursor hotspot coordinates.
|
||||||
|
*/
|
||||||
|
set_cursor_style_callback(callback: SetCursorStyleCallback): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Required.
|
||||||
|
*/
|
||||||
|
set_cursor_style_callback_context(context: unknown): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Optional
|
||||||
|
*/
|
||||||
|
remote_clipboard_changed_callback(callback: RemoteClipboardChangedCallback): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Optional
|
||||||
|
*/
|
||||||
|
remote_received_format_list_callback(callback: RemoteReceiveForwardListCallback): SessionBuilder;
|
||||||
|
/**
|
||||||
|
* Optional
|
||||||
|
*/
|
||||||
|
force_clipboard_update_callback(callback: ForceClipboardUpdateCallback): SessionBuilder;
|
||||||
|
extension(value: unknown): SessionBuilder;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
|
extension_call(_ident: string, _call: Function): SessionBuilder;
|
||||||
|
connect(): Promise<Session>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetCursorStyleCallback {
|
||||||
|
(
|
||||||
|
cursor_kind: string,
|
||||||
|
cursor_data: string | undefined,
|
||||||
|
hotspot_x: number | undefined,
|
||||||
|
hotspot_y: number | undefined,
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoteClipboardChangedCallback {
|
||||||
|
(transaction: ClipboardTransaction): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoteReceiveForwardListCallback {
|
||||||
|
(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForceClipboardUpdateCallback {
|
||||||
|
(): void;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface SessionTerminationInfo {
|
||||||
|
reason(): string;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import type { SessionEventType } from '../enums/SessionEventType';
|
import type { SessionEventType } from '../enums/SessionEventType';
|
||||||
|
|
||||||
export enum UserIronRdpErrorKind {
|
export enum RemoteDesktopErrorKind {
|
||||||
General = 0,
|
General = 0,
|
||||||
WrongPassword = 1,
|
WrongPassword = 1,
|
||||||
LogonFailure = 2,
|
LogonFailure = 2,
|
||||||
|
@ -8,12 +8,12 @@ export enum UserIronRdpErrorKind {
|
||||||
RDCleanPath = 4,
|
RDCleanPath = 4,
|
||||||
ProxyConnect = 5,
|
ProxyConnect = 5,
|
||||||
}
|
}
|
||||||
export interface UserIronRdpError {
|
export interface RemoteDesktopError {
|
||||||
backtrace: () => string;
|
backtrace: () => string;
|
||||||
kind: () => UserIronRdpErrorKind;
|
kind: () => RemoteDesktopErrorKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionEvent {
|
export interface SessionEvent {
|
||||||
type: SessionEventType;
|
type: SessionEventType;
|
||||||
data: UserIronRdpError | string;
|
data: RemoteDesktopError | string;
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<svelte:options
|
<svelte:options
|
||||||
customElement={{
|
customElement={{
|
||||||
tag: 'iron-remote-gui',
|
tag: 'iron-remote-desktop',
|
||||||
shadow: 'none',
|
shadow: 'none',
|
||||||
extend: (elementConstructor) => {
|
extend: (elementConstructor) => {
|
||||||
return class extends elementConstructor {
|
return class extends elementConstructor {
|
||||||
|
@ -16,23 +16,26 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { loggingService } from './services/logging.service';
|
import { loggingService } from './services/logging.service';
|
||||||
import { WasmBridgeService } from './services/wasm-bridge.service';
|
import { RemoteDesktopService } from './services/remote-desktop.service';
|
||||||
import { LogType } from './enums/LogType';
|
import { LogType } from './enums/LogType';
|
||||||
import type { ResizeEvent } from './interfaces/ResizeEvent';
|
import type { ResizeEvent } from './interfaces/ResizeEvent';
|
||||||
import { PublicAPI } from './services/PublicAPI';
|
import { PublicAPI } from './services/PublicAPI';
|
||||||
import { ScreenScale } from './enums/ScreenScale';
|
import { ScreenScale } from './enums/ScreenScale';
|
||||||
import { ClipboardContent, ClipboardTransaction } from '../../../crates/ironrdp-web/pkg/ironrdp_web';
|
import type { ClipboardTransaction } from './interfaces/ClipboardTransaction';
|
||||||
|
import type { RemoteDesktopModule } from './interfaces/RemoteDesktopModule';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
scale,
|
scale,
|
||||||
verbose,
|
verbose,
|
||||||
debugwasm,
|
debugwasm,
|
||||||
flexcenter,
|
flexcenter,
|
||||||
|
module,
|
||||||
}: {
|
}: {
|
||||||
scale: string;
|
scale: string;
|
||||||
verbose: 'true' | 'false';
|
verbose: 'true' | 'false';
|
||||||
debugwasm: 'OFF' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
|
debugwasm: 'OFF' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
|
||||||
flexcenter: string;
|
flexcenter: string;
|
||||||
|
module: RemoteDesktopModule;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let isVisible = $state(false);
|
let isVisible = $state(false);
|
||||||
|
@ -51,9 +54,8 @@
|
||||||
|
|
||||||
let viewerStyle = $state('');
|
let viewerStyle = $state('');
|
||||||
let wrapperStyle = $state('');
|
let wrapperStyle = $state('');
|
||||||
|
let remoteDesktopService = new RemoteDesktopService(module);
|
||||||
let wasmService = new WasmBridgeService();
|
let publicAPI = new PublicAPI(remoteDesktopService);
|
||||||
let publicAPI = new PublicAPI(wasmService);
|
|
||||||
|
|
||||||
// Firefox's clipboard API is very limited, and doesn't support reading from the clipboard
|
// Firefox's clipboard API is very limited, and doesn't support reading from the clipboard
|
||||||
// without changing browser settings via `about:config`.
|
// without changing browser settings via `about:config`.
|
||||||
|
@ -102,12 +104,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirefox) {
|
if (isFirefox) {
|
||||||
wasmService.setOnRemoteClipboardChanged(ffOnRemoteClipboardChanged);
|
remoteDesktopService.setOnRemoteClipboardChanged(ffOnRemoteClipboardChanged);
|
||||||
wasmService.setOnRemoteReceivedFormatList(ffOnRemoteReceivedFormatList);
|
remoteDesktopService.setOnRemoteReceivedFormatList(ffOnRemoteReceivedFormatList);
|
||||||
wasmService.setOnForceClipboardUpdate(onForceClipboardUpdate);
|
remoteDesktopService.setOnForceClipboardUpdate(onForceClipboardUpdate);
|
||||||
} else if (isClipboardApiSupported) {
|
} else if (isClipboardApiSupported) {
|
||||||
wasmService.setOnRemoteClipboardChanged(onRemoteClipboardChanged);
|
remoteDesktopService.setOnRemoteClipboardChanged(onRemoteClipboardChanged);
|
||||||
wasmService.setOnForceClipboardUpdate(onForceClipboardUpdate);
|
remoteDesktopService.setOnForceClipboardUpdate(onForceClipboardUpdate);
|
||||||
|
|
||||||
// Start the clipboard monitoring loop
|
// Start the clipboard monitoring loop
|
||||||
setTimeout(onMonitorClipboard, CLIPBOARD_MONITORING_INTERVAL);
|
setTimeout(onMonitorClipboard, CLIPBOARD_MONITORING_INTERVAL);
|
||||||
|
@ -135,12 +137,9 @@
|
||||||
let result = {} as Record<string, Blob>;
|
let result = {} as Record<string, Blob>;
|
||||||
|
|
||||||
for (const item of transaction.content()) {
|
for (const item of transaction.content()) {
|
||||||
if (!(item instanceof ClipboardContent)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mime = item.mime_type();
|
let mime = item.mime_type();
|
||||||
let value = new Blob([item.value()], { type: mime });
|
let value = new Blob([item.value()], { type: mime });
|
||||||
|
|
||||||
result[mime] = value;
|
result[mime] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,9 +150,9 @@
|
||||||
function onForceClipboardUpdate() {
|
function onForceClipboardUpdate() {
|
||||||
try {
|
try {
|
||||||
if (lastClientClipboardTransaction) {
|
if (lastClientClipboardTransaction) {
|
||||||
wasmService.onClipboardChanged(lastClientClipboardTransaction);
|
remoteDesktopService.onClipboardChanged(lastClientClipboardTransaction);
|
||||||
} else {
|
} else {
|
||||||
wasmService.onClipboardChanged(ClipboardTransaction.new());
|
remoteDesktopService.onClipboardChangedEmpty();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to send initial clipboard state: ' + err);
|
console.error('Failed to send initial clipboard state: ' + err);
|
||||||
|
@ -237,7 +236,7 @@
|
||||||
if (!sameValue) {
|
if (!sameValue) {
|
||||||
lastClientClipboardItems = values;
|
lastClientClipboardItems = values;
|
||||||
|
|
||||||
let transaction = ClipboardTransaction.new();
|
let transaction = remoteDesktopService.constructClipboardTransaction();
|
||||||
|
|
||||||
// Iterate over `Record` type
|
// Iterate over `Record` type
|
||||||
values.forEach((value: string | Uint8Array, key: string) => {
|
values.forEach((value: string | Uint8Array, key: string) => {
|
||||||
|
@ -247,15 +246,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.startsWith('text/') && typeof value === 'string') {
|
if (key.startsWith('text/') && typeof value === 'string') {
|
||||||
transaction.add_content(ClipboardContent.new_text(key, value));
|
transaction.add_content(remoteDesktopService.constructClipboardContentFromText(key, value));
|
||||||
} else if (key.startsWith('image/') && value instanceof Uint8Array) {
|
} else if (key.startsWith('image/') && value instanceof Uint8Array) {
|
||||||
transaction.add_content(ClipboardContent.new_binary(key, value));
|
transaction.add_content(remoteDesktopService.constructClipboardContentFromBinary(key, value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!transaction.is_empty()) {
|
if (!transaction.is_empty()) {
|
||||||
lastClientClipboardTransaction = transaction;
|
lastClientClipboardTransaction = transaction;
|
||||||
wasmService.onClipboardChanged(transaction);
|
remoteDesktopService.onClipboardChanged(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -335,7 +334,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let transaction = ClipboardTransaction.new();
|
let transaction = remoteDesktopService.constructClipboardTransaction();
|
||||||
|
|
||||||
if (evt.clipboardData == null) {
|
if (evt.clipboardData == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -346,11 +345,11 @@
|
||||||
|
|
||||||
if (mime.startsWith('text/')) {
|
if (mime.startsWith('text/')) {
|
||||||
clipItem.getAsString((str: string) => {
|
clipItem.getAsString((str: string) => {
|
||||||
let content = ClipboardContent.new_text(mime, str);
|
let content = remoteDesktopService.constructClipboardContentFromText(mime, str);
|
||||||
transaction.add_content(content);
|
transaction.add_content(content);
|
||||||
|
|
||||||
if (!transaction.is_empty()) {
|
if (!transaction.is_empty()) {
|
||||||
wasmService.onClipboardChanged(transaction);
|
remoteDesktopService.onClipboardChanged(transaction as ClipboardTransaction);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -364,11 +363,11 @@
|
||||||
|
|
||||||
file.arrayBuffer().then((buffer: ArrayBuffer) => {
|
file.arrayBuffer().then((buffer: ArrayBuffer) => {
|
||||||
const strict_buffer = new Uint8Array(buffer);
|
const strict_buffer = new Uint8Array(buffer);
|
||||||
let content = ClipboardContent.new_binary(mime, strict_buffer);
|
let content = remoteDesktopService.constructClipboardContentFromBinary(mime, strict_buffer);
|
||||||
transaction.add_content(content);
|
transaction.add_content(content);
|
||||||
|
|
||||||
if (!transaction.is_empty()) {
|
if (!transaction.is_empty()) {
|
||||||
wasmService.onClipboardChanged(transaction);
|
remoteDesktopService.onClipboardChanged(transaction);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -454,7 +453,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function serverBridgeListeners() {
|
function serverBridgeListeners() {
|
||||||
wasmService.resize.subscribe((evt: ResizeEvent) => {
|
remoteDesktopService.resize.subscribe((evt: ResizeEvent) => {
|
||||||
loggingService.info(`Resize canvas to: ${evt.desktop_size.width}x${evt.desktop_size.height}`);
|
loggingService.info(`Resize canvas to: ${evt.desktop_size.width}x${evt.desktop_size.height}`);
|
||||||
canvas.width = evt.desktop_size.width;
|
canvas.width = evt.desktop_size.width;
|
||||||
canvas.height = evt.desktop_size.height;
|
canvas.height = evt.desktop_size.height;
|
||||||
|
@ -467,17 +466,17 @@
|
||||||
scaleSession(scale);
|
scaleSession(scale);
|
||||||
});
|
});
|
||||||
|
|
||||||
wasmService.scaleObserver.subscribe((s) => {
|
remoteDesktopService.scaleObserver.subscribe((s) => {
|
||||||
loggingService.info('Change scale!');
|
loggingService.info('Change scale!');
|
||||||
scaleSession(s);
|
scaleSession(s);
|
||||||
});
|
});
|
||||||
|
|
||||||
wasmService.dynamicResize.subscribe((evt) => {
|
remoteDesktopService.dynamicResize.subscribe((evt) => {
|
||||||
loggingService.info(`Dynamic resize!, width: ${evt.width}, height: ${evt.height}`);
|
loggingService.info(`Dynamic resize!, width: ${evt.width}, height: ${evt.height}`);
|
||||||
setViewerStyle(evt.width.toString(), evt.height.toString(), true);
|
setViewerStyle(evt.width.toString(), evt.height.toString(), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
wasmService.changeVisibilityObservable.subscribe((val) => {
|
remoteDesktopService.changeVisibilityObservable.subscribe((val) => {
|
||||||
isVisible = val;
|
isVisible = val;
|
||||||
if (val) {
|
if (val) {
|
||||||
//Enforce first scaling and delay the call to scaleSession to ensure Dom is ready.
|
//Enforce first scaling and delay the call to scaleSession to ensure Dom is ready.
|
||||||
|
@ -590,7 +589,7 @@
|
||||||
y: Math.round((evt.clientY - rect.top) * scaleY),
|
y: Math.round((evt.clientY - rect.top) * scaleY),
|
||||||
};
|
};
|
||||||
|
|
||||||
wasmService.updateMousePosition(coord);
|
remoteDesktopService.updateMousePosition(coord);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMouseButtonState(state: MouseEvent, isDown: boolean) {
|
function setMouseButtonState(state: MouseEvent, isDown: boolean) {
|
||||||
|
@ -610,20 +609,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wasmService.mouseButtonState(state, isDown, true);
|
remoteDesktopService.mouseButtonState(state, isDown, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mouseWheel(evt: WheelEvent) {
|
function mouseWheel(evt: WheelEvent) {
|
||||||
wasmService.mouseWheel(evt);
|
remoteDesktopService.mouseWheel(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMouseIn(evt: MouseEvent) {
|
function setMouseIn(evt: MouseEvent) {
|
||||||
canvas.focus();
|
canvas.focus();
|
||||||
wasmService.mouseIn(evt);
|
remoteDesktopService.mouseIn(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMouseOut(evt: MouseEvent) {
|
function setMouseOut(evt: MouseEvent) {
|
||||||
wasmService.mouseOut(evt);
|
remoteDesktopService.mouseOut(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyboardEvent(evt: KeyboardEvent) {
|
function keyboardEvent(evt: KeyboardEvent) {
|
||||||
|
@ -639,7 +638,7 @@
|
||||||
ffWaitForRemoteClipboardTransactionSet();
|
ffWaitForRemoteClipboardTransactionSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
wasmService.sendKeyboardEvent(evt);
|
remoteDesktopService.sendKeyboardEvent(evt);
|
||||||
|
|
||||||
// Propagate further
|
// Propagate further
|
||||||
return true;
|
return true;
|
||||||
|
@ -663,8 +662,8 @@
|
||||||
canvas.height = 600;
|
canvas.height = 600;
|
||||||
|
|
||||||
const logLevel = LogType[debugwasm] ?? LogType.INFO;
|
const logLevel = LogType[debugwasm] ?? LogType.INFO;
|
||||||
await wasmService.init(logLevel);
|
await remoteDesktopService.init(logLevel);
|
||||||
wasmService.setCanvas(canvas);
|
remoteDesktopService.setCanvas(canvas);
|
||||||
|
|
||||||
initListeners();
|
initListeners();
|
||||||
|
|
15
web-client/iron-remote-desktop/src/main.ts
Normal file
15
web-client/iron-remote-desktop/src/main.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
export * as default from './iron-remote-desktop.svelte';
|
||||||
|
export type { ResizeEvent } from './interfaces/ResizeEvent';
|
||||||
|
export type { NewSessionInfo } from './interfaces/NewSessionInfo';
|
||||||
|
export type { ServerRect } from './interfaces/ServerRect';
|
||||||
|
export type { DesktopSize } from './interfaces/DesktopSize';
|
||||||
|
export type { SessionEvent, RemoteDesktopError, RemoteDesktopErrorKind } from './interfaces/session-event';
|
||||||
|
export type { SessionEventType } from './enums/SessionEventType';
|
||||||
|
export type { SessionTerminationInfo } from './interfaces/SessionTerminationInfo';
|
||||||
|
export type { ClipboardTransaction } from './interfaces/ClipboardTransaction';
|
||||||
|
export type { ClipboardContent } from './interfaces/ClipboardContent';
|
||||||
|
export type { DeviceEvent } from './interfaces/DeviceEvent';
|
||||||
|
export type { InputTransaction } from './interfaces/InputTransaction';
|
||||||
|
export type { Session } from './interfaces/Session';
|
||||||
|
export type { SessionBuilder } from './interfaces/SessionBuilder';
|
||||||
|
export type { UserInteraction } from './interfaces/UserInteraction';
|
|
@ -1,16 +1,16 @@
|
||||||
import { loggingService } from './logging.service';
|
import { loggingService } from './logging.service';
|
||||||
import type { NewSessionInfo } from '../interfaces/NewSessionInfo';
|
import type { NewSessionInfo } from '../interfaces/NewSessionInfo';
|
||||||
import { SpecialCombination } from '../enums/SpecialCombination';
|
import { SpecialCombination } from '../enums/SpecialCombination';
|
||||||
import type { WasmBridgeService } from './wasm-bridge.service';
|
import { RemoteDesktopService } from './remote-desktop.service';
|
||||||
import type { UserInteraction } from '../interfaces/UserInteraction';
|
import type { UserInteraction } from '../interfaces/UserInteraction';
|
||||||
import type { ScreenScale } from '../enums/ScreenScale';
|
import type { ScreenScale } from '../enums/ScreenScale';
|
||||||
import type { DesktopSize } from '../interfaces/DesktopSize';
|
import type { DesktopSize } from '../interfaces/DesktopSize';
|
||||||
|
|
||||||
export class PublicAPI {
|
export class PublicAPI {
|
||||||
private wasmService: WasmBridgeService;
|
private remoteDesktopService: RemoteDesktopService;
|
||||||
|
|
||||||
constructor(wasmService: WasmBridgeService) {
|
constructor(remoteDesktopService: RemoteDesktopService) {
|
||||||
this.wasmService = wasmService;
|
this.remoteDesktopService = remoteDesktopService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private connect(
|
private connect(
|
||||||
|
@ -26,7 +26,7 @@ export class PublicAPI {
|
||||||
use_display_control = false,
|
use_display_control = false,
|
||||||
): Promise<NewSessionInfo> {
|
): Promise<NewSessionInfo> {
|
||||||
loggingService.info('Initializing connection.');
|
loggingService.info('Initializing connection.');
|
||||||
const resultObservable = this.wasmService.connect(
|
const resultObservable = this.remoteDesktopService.connect(
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
destination,
|
destination,
|
||||||
|
@ -43,40 +43,40 @@ export class PublicAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ctrlAltDel() {
|
private ctrlAltDel() {
|
||||||
this.wasmService.sendSpecialCombination(SpecialCombination.CTRL_ALT_DEL);
|
this.remoteDesktopService.sendSpecialCombination(SpecialCombination.CTRL_ALT_DEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private metaKey() {
|
private metaKey() {
|
||||||
this.wasmService.sendSpecialCombination(SpecialCombination.META);
|
this.remoteDesktopService.sendSpecialCombination(SpecialCombination.META);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setVisibility(state: boolean) {
|
private setVisibility(state: boolean) {
|
||||||
loggingService.info(`Change component visibility to: ${state}`);
|
loggingService.info(`Change component visibility to: ${state}`);
|
||||||
this.wasmService.setVisibility(state);
|
this.remoteDesktopService.setVisibility(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setScale(scale: ScreenScale) {
|
private setScale(scale: ScreenScale) {
|
||||||
this.wasmService.setScale(scale);
|
this.remoteDesktopService.setScale(scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private shutdown() {
|
private shutdown() {
|
||||||
this.wasmService.shutdown();
|
this.remoteDesktopService.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setKeyboardUnicodeMode(use_unicode: boolean) {
|
private setKeyboardUnicodeMode(use_unicode: boolean) {
|
||||||
this.wasmService.setKeyboardUnicodeMode(use_unicode);
|
this.remoteDesktopService.setKeyboardUnicodeMode(use_unicode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCursorStyleOverride(style: string | null) {
|
private setCursorStyleOverride(style: string | null) {
|
||||||
this.wasmService.setCursorStyleOverride(style);
|
this.remoteDesktopService.setCursorStyleOverride(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
private resize(width: number, height: number, scale?: number) {
|
private resize(width: number, height: number, scale?: number) {
|
||||||
this.wasmService.resizeDynamic(width, height, scale);
|
this.remoteDesktopService.resizeDynamic(width, height, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setEnableClipboard(enable: boolean) {
|
private setEnableClipboard(enable: boolean) {
|
||||||
this.wasmService.setEnableClipboard(enable);
|
this.remoteDesktopService.setEnableClipboard(enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
getExposedFunctions(): UserInteraction {
|
getExposedFunctions(): UserInteraction {
|
||||||
|
@ -85,7 +85,7 @@ export class PublicAPI {
|
||||||
connect: this.connect.bind(this),
|
connect: this.connect.bind(this),
|
||||||
setScale: this.setScale.bind(this),
|
setScale: this.setScale.bind(this),
|
||||||
onSessionEvent: (callback) => {
|
onSessionEvent: (callback) => {
|
||||||
this.wasmService.sessionObserver.subscribe(callback);
|
this.remoteDesktopService.sessionObserver.subscribe(callback);
|
||||||
},
|
},
|
||||||
ctrlAltDel: this.ctrlAltDel.bind(this),
|
ctrlAltDel: this.ctrlAltDel.bind(this),
|
||||||
metaKey: this.metaKey.bind(this),
|
metaKey: this.metaKey.bind(this),
|
|
@ -1,15 +1,4 @@
|
||||||
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
|
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
|
||||||
import init, {
|
|
||||||
DesktopSize,
|
|
||||||
DeviceEvent,
|
|
||||||
InputTransaction,
|
|
||||||
ironrdp_init,
|
|
||||||
IronRdpError,
|
|
||||||
Session,
|
|
||||||
SessionBuilder,
|
|
||||||
ClipboardTransaction,
|
|
||||||
SessionTerminationInfo,
|
|
||||||
} from '../../../../crates/ironrdp-web/pkg/ironrdp_web';
|
|
||||||
import { loggingService } from './logging.service';
|
import { loggingService } from './logging.service';
|
||||||
import { catchError, filter, map } from 'rxjs/operators';
|
import { catchError, filter, map } from 'rxjs/operators';
|
||||||
import { scanCode } from '../lib/scancodes';
|
import { scanCode } from '../lib/scancodes';
|
||||||
|
@ -23,14 +12,21 @@ import { SpecialCombination } from '../enums/SpecialCombination';
|
||||||
import type { ResizeEvent } from '../interfaces/ResizeEvent';
|
import type { ResizeEvent } from '../interfaces/ResizeEvent';
|
||||||
import { ScreenScale } from '../enums/ScreenScale';
|
import { ScreenScale } from '../enums/ScreenScale';
|
||||||
import type { MousePosition } from '../interfaces/MousePosition';
|
import type { MousePosition } from '../interfaces/MousePosition';
|
||||||
import type { SessionEvent, UserIronRdpErrorKind } from '../interfaces/session-event';
|
import type { SessionEvent, RemoteDesktopErrorKind, RemoteDesktopError } from '../interfaces/session-event';
|
||||||
import type { DesktopSize as IDesktopSize } from '../interfaces/DesktopSize';
|
import type { DesktopSize } from '../interfaces/DesktopSize';
|
||||||
|
import type { ClipboardTransaction } from '../interfaces/ClipboardTransaction';
|
||||||
|
import type { ClipboardContent } from '../interfaces/ClipboardContent';
|
||||||
|
import type { Session } from '../interfaces/Session';
|
||||||
|
import type { DeviceEvent } from '../interfaces/DeviceEvent';
|
||||||
|
import type { SessionTerminationInfo } from '../interfaces/SessionTerminationInfo';
|
||||||
|
import type { RemoteDesktopModule } from '../interfaces/RemoteDesktopModule';
|
||||||
|
|
||||||
type OnRemoteClipboardChanged = (transaction: ClipboardTransaction) => void;
|
type OnRemoteClipboardChanged = (transaction: ClipboardTransaction) => void;
|
||||||
type OnRemoteReceivedFormatsList = () => void;
|
type OnRemoteReceivedFormatsList = () => void;
|
||||||
type OnForceClipboardUpdate = () => void;
|
type OnForceClipboardUpdate = () => void;
|
||||||
|
|
||||||
export class WasmBridgeService {
|
export class RemoteDesktopService {
|
||||||
|
private module: RemoteDesktopModule;
|
||||||
private _resize: Subject<ResizeEvent> = new Subject<ResizeEvent>();
|
private _resize: Subject<ResizeEvent> = new Subject<ResizeEvent>();
|
||||||
private mousePosition: BehaviorSubject<MousePosition> = new BehaviorSubject<MousePosition>({
|
private mousePosition: BehaviorSubject<MousePosition> = new BehaviorSubject<MousePosition>({
|
||||||
x: 0,
|
x: 0,
|
||||||
|
@ -62,16 +58,29 @@ export class WasmBridgeService {
|
||||||
height: number;
|
height: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
constructor() {
|
constructor(module: RemoteDesktopModule) {
|
||||||
this.resize = this._resize.asObservable();
|
this.resize = this._resize.asObservable();
|
||||||
|
this.module = module;
|
||||||
loggingService.info('Web bridge initialized.');
|
loggingService.info('Web bridge initialized.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructClipboardTransaction(): ClipboardTransaction {
|
||||||
|
return this.module.ClipboardTransaction.construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructClipboardContentFromText(mime_type: string, text: string): ClipboardContent {
|
||||||
|
return this.module.ClipboardContent.new_text(mime_type, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructClipboardContentFromBinary(mime_type: string, binary: Uint8Array): ClipboardContent {
|
||||||
|
return this.module.ClipboardContent.new_binary(mime_type, binary);
|
||||||
|
}
|
||||||
|
|
||||||
async init(debug: LogType) {
|
async init(debug: LogType) {
|
||||||
loggingService.info('Loading wasm file.');
|
loggingService.info('Loading wasm file.');
|
||||||
await init();
|
await this.module.init();
|
||||||
loggingService.info('Initializing IronRDP.');
|
loggingService.info('Initializing IronRDP.');
|
||||||
ironrdp_init(LogType[debug]);
|
this.module.iron_init(LogType[debug]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If set to false, the clipboard will not be enabled and the callbacks will not be registered to the Rust side
|
// If set to false, the clipboard will not be enabled and the callbacks will not be registered to the Rust side
|
||||||
|
@ -115,12 +124,14 @@ export class WasmBridgeService {
|
||||||
if (preventDefault) {
|
if (preventDefault) {
|
||||||
event.preventDefault(); // prevent default behavior (context menu, etc)
|
event.preventDefault(); // prevent default behavior (context menu, etc)
|
||||||
}
|
}
|
||||||
const mouseFnc = isDown ? DeviceEvent.new_mouse_button_pressed : DeviceEvent.new_mouse_button_released;
|
const mouseFnc = isDown
|
||||||
|
? this.module.DeviceEvent.mouse_button_pressed
|
||||||
|
: this.module.DeviceEvent.mouse_button_released;
|
||||||
this.doTransactionFromDeviceEvents([mouseFnc(event.button)]);
|
this.doTransactionFromDeviceEvents([mouseFnc(event.button)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMousePosition(position: MousePosition) {
|
updateMousePosition(position: MousePosition) {
|
||||||
this.doTransactionFromDeviceEvents([DeviceEvent.new_mouse_move(position.x, position.y)]);
|
this.doTransactionFromDeviceEvents([this.module.DeviceEvent.mouse_move(position.x, position.y)]);
|
||||||
this.mousePosition.next(position);
|
this.mousePosition.next(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,12 +142,13 @@ export class WasmBridgeService {
|
||||||
proxyAddress: string,
|
proxyAddress: string,
|
||||||
serverDomain: string,
|
serverDomain: string,
|
||||||
authToken: string,
|
authToken: string,
|
||||||
desktopSize?: IDesktopSize,
|
desktopSize?: DesktopSize,
|
||||||
preConnectionBlob?: string,
|
preConnectionBlob?: string,
|
||||||
kdc_proxy_url?: string,
|
kdc_proxy_url?: string,
|
||||||
use_display_control = true,
|
use_display_control = true,
|
||||||
): Observable<NewSessionInfo> {
|
): Observable<NewSessionInfo> {
|
||||||
const sessionBuilder = SessionBuilder.new();
|
const sessionBuilder = this.module.SessionBuilder.construct();
|
||||||
|
|
||||||
sessionBuilder.proxy_address(proxyAddress);
|
sessionBuilder.proxy_address(proxyAddress);
|
||||||
sessionBuilder.destination(destination);
|
sessionBuilder.destination(destination);
|
||||||
sessionBuilder.server_domain(serverDomain);
|
sessionBuilder.server_domain(serverDomain);
|
||||||
|
@ -146,13 +158,13 @@ export class WasmBridgeService {
|
||||||
sessionBuilder.render_canvas(this.canvas!);
|
sessionBuilder.render_canvas(this.canvas!);
|
||||||
sessionBuilder.set_cursor_style_callback_context(this);
|
sessionBuilder.set_cursor_style_callback_context(this);
|
||||||
sessionBuilder.set_cursor_style_callback(this.setCursorStyleCallback);
|
sessionBuilder.set_cursor_style_callback(this.setCursorStyleCallback);
|
||||||
sessionBuilder.kdc_proxy_url(kdc_proxy_url);
|
sessionBuilder.extension({ DisplayControl: use_display_control });
|
||||||
if (use_display_control) {
|
|
||||||
sessionBuilder.use_display_control();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preConnectionBlob != null) {
|
if (preConnectionBlob != null) {
|
||||||
sessionBuilder.pcb(preConnectionBlob);
|
sessionBuilder.extension({ Pcb: preConnectionBlob });
|
||||||
|
}
|
||||||
|
if (kdc_proxy_url != null) {
|
||||||
|
sessionBuilder.extension({ KdcProxyUrl: kdc_proxy_url });
|
||||||
}
|
}
|
||||||
if (this.onRemoteClipboardChanged != null && this.enableClipboard) {
|
if (this.onRemoteClipboardChanged != null && this.enableClipboard) {
|
||||||
sessionBuilder.remote_clipboard_changed_callback(this.onRemoteClipboardChanged);
|
sessionBuilder.remote_clipboard_changed_callback(this.onRemoteClipboardChanged);
|
||||||
|
@ -165,21 +177,22 @@ export class WasmBridgeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desktopSize != null) {
|
if (desktopSize != null) {
|
||||||
sessionBuilder.desktop_size(DesktopSize.new(desktopSize.width, desktopSize.height));
|
sessionBuilder.desktop_size(this.module.DesktopSize.construct(desktopSize.width, desktopSize.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type guard to filter out errors
|
// Type guard to filter out errors
|
||||||
function isSession(result: IronRdpError | Session): result is Session {
|
function isSession(result: RemoteDesktopError | Session): result is Session {
|
||||||
return result instanceof Session;
|
// Check whether function exists. To make it more robust we can check every method.
|
||||||
|
return (<Session>result).run !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return from(sessionBuilder.connect()).pipe(
|
return from(sessionBuilder.connect()).pipe(
|
||||||
catchError((err: IronRdpError) => {
|
catchError((err: RemoteDesktopError) => {
|
||||||
this.raiseSessionEvent({
|
this.raiseSessionEvent({
|
||||||
type: SessionEventType.ERROR,
|
type: SessionEventType.ERROR,
|
||||||
data: {
|
data: {
|
||||||
backtrace: () => err.backtrace(),
|
backtrace: () => err.backtrace(),
|
||||||
kind: () => err.kind() as number as UserIronRdpErrorKind,
|
kind: () => err.kind() as number as RemoteDesktopErrorKind,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return of(err);
|
return of(err);
|
||||||
|
@ -188,7 +201,7 @@ export class WasmBridgeService {
|
||||||
map((session: Session) => {
|
map((session: Session) => {
|
||||||
from(session.run())
|
from(session.run())
|
||||||
.pipe(
|
.pipe(
|
||||||
catchError((err) => {
|
catchError((err: RemoteDesktopError) => {
|
||||||
this.setVisibility(false);
|
this.setVisibility(false);
|
||||||
this.raiseSessionEvent({
|
this.raiseSessionEvent({
|
||||||
type: SessionEventType.ERROR,
|
type: SessionEventType.ERROR,
|
||||||
|
@ -245,7 +258,7 @@ export class WasmBridgeService {
|
||||||
mouseWheel(event: WheelEvent) {
|
mouseWheel(event: WheelEvent) {
|
||||||
const vertical = event.deltaY !== 0;
|
const vertical = event.deltaY !== 0;
|
||||||
const rotation = vertical ? event.deltaY : event.deltaX;
|
const rotation = vertical ? event.deltaY : event.deltaX;
|
||||||
this.doTransactionFromDeviceEvents([DeviceEvent.new_wheel_rotations(vertical, -rotation)]);
|
this.doTransactionFromDeviceEvents([this.module.DeviceEvent.wheel_rotations(vertical, -rotation)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisibility(state: boolean) {
|
setVisibility(state: boolean) {
|
||||||
|
@ -274,6 +287,13 @@ export class WasmBridgeService {
|
||||||
return onClipboardChangedPromise();
|
return onClipboardChangedPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClipboardChangedEmpty(): Promise<void> {
|
||||||
|
const onClipboardChangedPromise = async () => {
|
||||||
|
await this.session?.on_clipboard_paste(this.module.ClipboardTransaction.construct());
|
||||||
|
};
|
||||||
|
return onClipboardChangedPromise();
|
||||||
|
}
|
||||||
|
|
||||||
setKeyboardUnicodeMode(use_unicode: boolean) {
|
setKeyboardUnicodeMode(use_unicode: boolean) {
|
||||||
this.keyboardUnicodeMode = use_unicode;
|
this.keyboardUnicodeMode = use_unicode;
|
||||||
}
|
}
|
||||||
|
@ -314,11 +334,11 @@ export class WasmBridgeService {
|
||||||
let unicodeEvent;
|
let unicodeEvent;
|
||||||
|
|
||||||
if (evt.type === 'keydown') {
|
if (evt.type === 'keydown') {
|
||||||
keyEvent = DeviceEvent.new_key_pressed;
|
keyEvent = this.module.DeviceEvent.key_pressed;
|
||||||
unicodeEvent = DeviceEvent.new_unicode_pressed;
|
unicodeEvent = this.module.DeviceEvent.unicode_pressed;
|
||||||
} else if (evt.type === 'keyup') {
|
} else if (evt.type === 'keyup') {
|
||||||
keyEvent = DeviceEvent.new_key_released;
|
keyEvent = this.module.DeviceEvent.key_released;
|
||||||
unicodeEvent = DeviceEvent.new_unicode_released;
|
unicodeEvent = this.module.DeviceEvent.unicode_released;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sendAsUnicode = true;
|
let sendAsUnicode = true;
|
||||||
|
@ -449,7 +469,7 @@ export class WasmBridgeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private doTransactionFromDeviceEvents(deviceEvents: DeviceEvent[]) {
|
private doTransactionFromDeviceEvents(deviceEvents: DeviceEvent[]) {
|
||||||
const transaction = InputTransaction.new();
|
const transaction = this.module.InputTransaction.construct();
|
||||||
deviceEvents.forEach((event) => transaction.add_event(event));
|
deviceEvents.forEach((event) => transaction.add_event(event));
|
||||||
this.session?.apply_inputs(transaction);
|
this.session?.apply_inputs(transaction);
|
||||||
}
|
}
|
||||||
|
@ -460,18 +480,21 @@ export class WasmBridgeService {
|
||||||
const suppr = parseInt('0xE053', 16);
|
const suppr = parseInt('0xE053', 16);
|
||||||
|
|
||||||
this.doTransactionFromDeviceEvents([
|
this.doTransactionFromDeviceEvents([
|
||||||
DeviceEvent.new_key_pressed(ctrl),
|
this.module.DeviceEvent.key_pressed(ctrl),
|
||||||
DeviceEvent.new_key_pressed(alt),
|
this.module.DeviceEvent.key_pressed(alt),
|
||||||
DeviceEvent.new_key_pressed(suppr),
|
this.module.DeviceEvent.key_pressed(suppr),
|
||||||
DeviceEvent.new_key_released(ctrl),
|
this.module.DeviceEvent.key_released(ctrl),
|
||||||
DeviceEvent.new_key_released(alt),
|
this.module.DeviceEvent.key_released(alt),
|
||||||
DeviceEvent.new_key_released(suppr),
|
this.module.DeviceEvent.key_released(suppr),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendMeta() {
|
private sendMeta() {
|
||||||
const meta = parseInt('0xE05B', 16);
|
const meta = parseInt('0xE05B', 16);
|
||||||
|
|
||||||
this.doTransactionFromDeviceEvents([DeviceEvent.new_key_pressed(meta), DeviceEvent.new_key_released(meta)]);
|
this.doTransactionFromDeviceEvents([
|
||||||
|
this.module.DeviceEvent.key_pressed(meta),
|
||||||
|
this.module.DeviceEvent.key_released(meta),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
8
web-client/iron-remote-desktop/tsconfig.node.json
Normal file
8
web-client/iron-remote-desktop/tsconfig.node.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ export default defineConfig({
|
||||||
build: {
|
build: {
|
||||||
lib: {
|
lib: {
|
||||||
entry: './src/main.ts',
|
entry: './src/main.ts',
|
||||||
name: 'IronRemoteGui',
|
name: 'IronRemoteDesktop',
|
||||||
formats: ['es'],
|
formats: ['es'],
|
||||||
},
|
},
|
||||||
},
|
},
|
|
@ -1,27 +0,0 @@
|
||||||
import { spawn } from 'child_process';
|
|
||||||
|
|
||||||
let run = async function (command, cwd) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const buildCommand = spawn(command, {
|
|
||||||
stdio: 'pipe',
|
|
||||||
shell: true,
|
|
||||||
cwd: cwd,
|
|
||||||
env: { ...process.env, RUSTFLAGS: '-Ctarget-feature=+simd128' },
|
|
||||||
});
|
|
||||||
|
|
||||||
buildCommand.stdout.on('data', (data) => {
|
|
||||||
console.log(`${data}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
buildCommand.stderr.on('data', (data) => {
|
|
||||||
console.error(`${data}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
buildCommand.on('close', (code) => {
|
|
||||||
console.log(`child process exited with code ${code}`);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
await run('wasm-pack build --target web', '../../crates/ironrdp-web');
|
|
|
@ -1,9 +0,0 @@
|
||||||
export * as default from './iron-remote-gui.svelte';
|
|
||||||
|
|
||||||
export type { UserInteraction } from './interfaces/UserInteraction';
|
|
||||||
export type { ResizeEvent } from './interfaces/ResizeEvent';
|
|
||||||
export type { NewSessionInfo } from './interfaces/NewSessionInfo';
|
|
||||||
export type { ServerRect } from './interfaces/ServerRect';
|
|
||||||
export type { DesktopSize } from './interfaces/DesktopSize';
|
|
||||||
export type { SessionEvent, UserIronRdpError, UserIronRdpErrorKind } from './interfaces/session-event';
|
|
||||||
export type { SessionEventType } from './enums/SessionEventType';
|
|
3
web-client/iron-svelte-client/.gitignore
vendored
3
web-client/iron-svelte-client/.gitignore
vendored
|
@ -3,4 +3,5 @@
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
/static/iron-remote-gui/
|
/static/iron-remote-desktop-rdp/
|
||||||
|
/static/iron-remote-desktop/
|
|
@ -1,7 +1,7 @@
|
||||||
# SvelteKit UI for IronRDP
|
# SvelteKit UI for IronRDP
|
||||||
|
|
||||||
Web-based frontend using [`SvelteKit`](https://kit.svelte.dev/) and [`Material`](https://material.io) frameworks.
|
Web-based frontend using [`SvelteKit`](https://kit.svelte.dev/) and [`Material`](https://material.io) frameworks.
|
||||||
This is a simple wrapper around the `iron-remote-gui` Web Component demonstrating how to use the API.
|
This is a simple wrapper around the `iron-remote-desktop` Web Component demonstrating how to use the API.
|
||||||
|
|
||||||
Note that this demonstration client is not intended to be used in production as-is.
|
Note that this demonstration client is not intended to be used in production as-is.
|
||||||
Devolutions is shipping well-integrated, production-ready IronRDP web clients as part of:
|
Devolutions is shipping well-integrated, production-ready IronRDP web clients as part of:
|
||||||
|
@ -111,13 +111,13 @@ If you have a Rust toolchain available, you can use the [`tokengen`][tokengen] t
|
||||||
|
|
||||||
## Run in development mode
|
## Run in development mode
|
||||||
|
|
||||||
First, run `npm install` in the [iron-remote-gui](../iron-remote-gui/) folder, and then `npm install` in [iron-svelte-client](./) folder.
|
First, run `npm install` in [iron-remote-desktop](../iron-remote-desktop) and [iron-remote-desktop-rdp](../iron-remote-desktop-rdp) folders, and then `npm install` in [iron-svelte-client](./) folder.
|
||||||
|
|
||||||
You can then start the dev server with either:
|
You can then start the dev server with either:
|
||||||
|
|
||||||
- `npm run dev` - Runs only the final application.
|
- `npm run dev` - Runs only the final application.
|
||||||
- `npm run dev-all` - Builds WASM module and `iron-remote-gui` prior to starting the dev server.
|
- `npm run dev-all` - Builds WASM module and `iron-remote-desktop` prior to starting the dev server.
|
||||||
- `npm run dev-no-wasm` - Only builds `iron-remote-gui` prior to starting the dev server.
|
- `npm run dev-no-wasm` - Only builds `iron-remote-desktop` prior to starting the dev server.
|
||||||
|
|
||||||
You can build distribution files with `npm run build`.
|
You can build distribution files with `npm run build`.
|
||||||
Files are to be found in `./iron-svelte-client/build/browser`.
|
Files are to be found in `./iron-svelte-client/build/browser`.
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import * as path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import { argv } from 'node:process';
|
import { argv } from 'node:process';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
|
|
||||||
let noWasm = false;
|
let noWasm = false;
|
||||||
|
|
||||||
let assetIronRemoteGuiFolder = './static/iron-remote-gui';
|
const assetIronRemoteDesktopFolder = './static/iron-remote-desktop';
|
||||||
|
const assetIronRemoteDesktopRdpFolder = './static/iron-remote-desktop-rdp';
|
||||||
|
|
||||||
argv.forEach((val, index) => {
|
argv.forEach((val, index) => {
|
||||||
if (index === 2 && val === 'no-wasm') {
|
if (index === 2 && val === 'no-wasm') {
|
||||||
|
@ -17,8 +13,8 @@ argv.forEach((val, index) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let run = async function (command, cwd) {
|
const run = async (command, cwd) => {
|
||||||
return new Promise((resolve) => {
|
try {
|
||||||
const buildCommand = spawn(command, { stdio: 'pipe', shell: true, cwd: cwd });
|
const buildCommand = spawn(command, { stdio: 'pipe', shell: true, cwd: cwd });
|
||||||
|
|
||||||
buildCommand.stdout.on('data', (data) => {
|
buildCommand.stdout.on('data', (data) => {
|
||||||
|
@ -29,29 +25,36 @@ let run = async function (command, cwd) {
|
||||||
console.error(`${data}`);
|
console.error(`${data}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
buildCommand.on('close', (code) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.log(`child process exited with code ${code}`);
|
buildCommand.on('close', (code) => {
|
||||||
resolve();
|
if (code !== 0) {
|
||||||
|
reject(new Error(`Process exited with code ${code}`));
|
||||||
|
} else {
|
||||||
|
console.log(`Child process exited successfully with code ${code}`);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} catch (err) {
|
||||||
|
console.error(`Failed to execute the process: ${err}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let copyCoreFiles = async function () {
|
const copyCoreFiles = async () => {
|
||||||
console.log('Copying core files…');
|
try {
|
||||||
await fs.remove(assetIronRemoteGuiFolder);
|
console.log('Copying core files…');
|
||||||
return new Promise((resolve) => {
|
await fs.remove(assetIronRemoteDesktopFolder);
|
||||||
let source = '../iron-remote-gui/dist';
|
await fs.remove(assetIronRemoteDesktopRdpFolder);
|
||||||
let destination = assetIronRemoteGuiFolder;
|
|
||||||
|
|
||||||
fs.copy(source, destination, function (err) {
|
const source = '../iron-remote-desktop/dist';
|
||||||
if (err) {
|
const sourceRdp = '../iron-remote-desktop-rdp/dist';
|
||||||
console.log('An error occurred while copying core files.');
|
|
||||||
return console.error(err);
|
await fs.copy(source, assetIronRemoteDesktopFolder);
|
||||||
}
|
await fs.copy(sourceRdp, assetIronRemoteDesktopRdpFolder);
|
||||||
console.log('Core files were copied successfully');
|
console.log('Core files were copied successfully');
|
||||||
resolve();
|
} catch (err) {
|
||||||
});
|
console.error(`An error occurred while copying core files: ${err}`);
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let buildCommand = 'npm run build';
|
let buildCommand = 'npm run build';
|
||||||
|
@ -59,5 +62,6 @@ if (noWasm) {
|
||||||
buildCommand = 'npm run build-alone';
|
buildCommand = 'npm run build-alone';
|
||||||
}
|
}
|
||||||
|
|
||||||
await run(buildCommand, '../iron-remote-gui');
|
await run(buildCommand, '../iron-remote-desktop');
|
||||||
|
await run(buildCommand, '../iron-remote-desktop-rdp');
|
||||||
await copyCoreFiles();
|
await copyCoreFiles();
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<link href="%sveltekit.assets%/beercss/beer.min.css" rel="stylesheet" />
|
<link href="%sveltekit.assets%/beercss/beer.min.css" rel="stylesheet" />
|
||||||
<link href="%sveltekit.assets%/theme.css" rel="stylesheet" />
|
<link href="%sveltekit.assets%/theme.css" rel="stylesheet" />
|
||||||
<script src="%sveltekit.assets%/beercss/beer.min.js" type="text/javascript"></script>
|
<script src="%sveltekit.assets%/beercss/beer.min.js" type="text/javascript"></script>
|
||||||
<script type="module" src="%sveltekit.assets%/iron-remote-gui/iron-remote-gui.js"></script>
|
<script type="module" src="%sveltekit.assets%/iron-remote-desktop/iron-remote-desktop.js"></script>
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { currentSession, userInteractionService } from '../../services/session.service';
|
import { currentSession, userInteractionService } from '../../services/session.service';
|
||||||
import { catchError, filter } from 'rxjs/operators';
|
import { catchError, filter } from 'rxjs/operators';
|
||||||
import type { UserInteraction, NewSessionInfo } from '../../../static/iron-remote-gui';
|
import type { UserInteraction, NewSessionInfo } from '../../../static/iron-remote-desktop';
|
||||||
import { from, of } from 'rxjs';
|
import { from, of } from 'rxjs';
|
||||||
import { toast } from '$lib/messages/message-store';
|
import { toast } from '$lib/messages/message-store';
|
||||||
import { showLogin } from '$lib/login/login-store';
|
import { showLogin } from '$lib/login/login-store';
|
||||||
import type { DesktopSize } from '../../models/desktop-size';
|
import { DesktopSize } from '../../models/desktop-size';
|
||||||
|
|
||||||
let username = 'Administrator';
|
let username = 'Administrator';
|
||||||
let password = 'DevoLabs123!';
|
let password = 'DevoLabs123!';
|
||||||
|
@ -14,10 +14,7 @@
|
||||||
let domain = '';
|
let domain = '';
|
||||||
let authtoken = '';
|
let authtoken = '';
|
||||||
let kdc_proxy_url = '';
|
let kdc_proxy_url = '';
|
||||||
let desktopSize: DesktopSize = {
|
let desktopSize = new DesktopSize(1280, 768);
|
||||||
width: 1280,
|
|
||||||
height: 768,
|
|
||||||
};
|
|
||||||
let pcb: string;
|
let pcb: string;
|
||||||
let pop_up = false;
|
let pop_up = false;
|
||||||
let enable_clipboard = true;
|
let enable_clipboard = true;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { setCurrentSessionActive, userInteractionService } from '../../services/session.service';
|
import { setCurrentSessionActive, userInteractionService } from '../../services/session.service';
|
||||||
import type { UserInteraction } from '../../../static/iron-remote-gui';
|
import type { UserInteraction } from '../../../static/iron-remote-desktop';
|
||||||
|
import IronRdp from '../../../static/iron-remote-desktop-rdp';
|
||||||
|
|
||||||
let uiService: UserInteraction;
|
let uiService: UserInteraction;
|
||||||
let cursorOverrideActive = false;
|
let cursorOverrideActive = false;
|
||||||
|
@ -93,10 +94,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const el = document.querySelector('iron-remote-gui');
|
const el = document.querySelector('iron-remote-desktop');
|
||||||
|
|
||||||
if (el == null) {
|
if (el == null) {
|
||||||
throw '`iron-remote-gui` element not found';
|
throw '`iron-remote-desktop` element not found';
|
||||||
}
|
}
|
||||||
|
|
||||||
el.addEventListener('ready', (e) => {
|
el.addEventListener('ready', (e) => {
|
||||||
|
@ -110,11 +111,7 @@
|
||||||
id="popup-screen"
|
id="popup-screen"
|
||||||
style="display: flex; height: 100%; flex-direction: column; background-color: #2e2e2e; position: relative"
|
style="display: flex; height: 100%; flex-direction: column; background-color: #2e2e2e; position: relative"
|
||||||
on:mousemove={(event) => {
|
on:mousemove={(event) => {
|
||||||
if (event.clientY < 100) {
|
showUtilityBar = event.clientY < 100;
|
||||||
showUtilityBar = true;
|
|
||||||
} else {
|
|
||||||
showUtilityBar = false;
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="tool-bar" class:hidden={!showUtilityBar}>
|
<div class="tool-bar" class:hidden={!showUtilityBar}>
|
||||||
|
@ -139,7 +136,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<iron-remote-gui debugwasm="INFO" verbose="true" scale="fit" flexcenter="true" />
|
<iron-remote-desktop debugwasm="INFO" verbose="true" scale="fit" flexcenter="true" module={IronRdp} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { setCurrentSessionActive, userInteractionService } from '../../services/session.service';
|
import { setCurrentSessionActive, userInteractionService } from '../../services/session.service';
|
||||||
import { showLogin } from '$lib/login/login-store';
|
import { showLogin } from '$lib/login/login-store';
|
||||||
import type { UserInteraction } from '../../../static/iron-remote-gui';
|
import type { UserInteraction } from '../../../static/iron-remote-desktop';
|
||||||
|
import IronRdp from '../../../static/iron-remote-desktop-rdp';
|
||||||
|
|
||||||
let uiService: UserInteraction;
|
let uiService: UserInteraction;
|
||||||
let cursorOverrideActive = false;
|
let cursorOverrideActive = false;
|
||||||
|
@ -47,10 +48,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let el = document.querySelector('iron-remote-gui');
|
let el = document.querySelector('iron-remote-desktop');
|
||||||
|
|
||||||
if (el == null) {
|
if (el == null) {
|
||||||
throw '`iron-remote-gui` element not found';
|
throw '`iron-remote-desktop` element not found';
|
||||||
}
|
}
|
||||||
|
|
||||||
el.addEventListener('ready', (e) => {
|
el.addEventListener('ready', (e) => {
|
||||||
|
@ -100,7 +101,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<iron-remote-gui debugwasm="INFO" verbose="true" scale="fit" flexcenter="true" />
|
<iron-remote-desktop debugwasm="INFO" verbose="true" scale="fit" flexcenter="true" module={IronRdp} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
export class DesktopSize {
|
import type { DesktopSize as IDesktopSize } from './../../../iron-remote-desktop/src/interfaces/DesktopSize';
|
||||||
width!: number;
|
export class DesktopSize implements IDesktopSize {
|
||||||
height!: number;
|
constructor(width: number, height: number) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
construct(width: number, height: number): DesktopSize {
|
||||||
|
return new DesktopSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
export class Rect {
|
|
||||||
top!: number;
|
|
||||||
left!: number;
|
|
||||||
width!: number;
|
|
||||||
height!: number;
|
|
||||||
buffer!: ArrayBuffer;
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ import type { Guid } from 'guid-typescript';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import { Session } from '../models/session';
|
import { Session } from '../models/session';
|
||||||
import type { UserInteraction } from '../../static/iron-remote-gui';
|
import type { UserInteraction } from '../../static/iron-remote-desktop';
|
||||||
|
|
||||||
export const userInteractionService: Writable<UserInteraction> = writable();
|
export const userInteractionService: Writable<UserInteraction> = writable();
|
||||||
export const currentSession: Writable<Session> = writable();
|
export const currentSession: Writable<Session> = writable();
|
||||||
|
|
|
@ -6,6 +6,11 @@ import topLevelAwait from 'vite-plugin-top-level-await';
|
||||||
const config: UserConfig = {
|
const config: UserConfig = {
|
||||||
mode: 'process.env.MODE' || 'development',
|
mode: 'process.env.MODE' || 'development',
|
||||||
plugins: [sveltekit(), wasm(), topLevelAwait()],
|
plugins: [sveltekit(), wasm(), topLevelAwait()],
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
allow: ['./static'],
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
@ -76,7 +76,8 @@ pub fn lock_files(sh: &Shell) -> anyhow::Result<()> {
|
||||||
const LOCK_FILES: &[&str] = &[
|
const LOCK_FILES: &[&str] = &[
|
||||||
"Cargo.lock",
|
"Cargo.lock",
|
||||||
"fuzz/Cargo.lock",
|
"fuzz/Cargo.lock",
|
||||||
"web-client/iron-remote-gui/package-lock.json",
|
"web-client/iron-remote-desktop/package-lock.json",
|
||||||
|
"web-client/iron-remote-desktop-rdp/package-lock.json",
|
||||||
"web-client/iron-svelte-client/package-lock.json",
|
"web-client/iron-svelte-client/package-lock.json",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,10 @@ pub fn workspace(sh: &Shell) -> anyhow::Result<()> {
|
||||||
println!("Done.");
|
println!("Done.");
|
||||||
|
|
||||||
println!("Remove npm folders…");
|
println!("Remove npm folders…");
|
||||||
sh.remove_path("./web-client/iron-remote-gui/node_modules")?;
|
sh.remove_path("./web-client/iron-remote-desktop/node_modules")?;
|
||||||
sh.remove_path("./web-client/iron-remote-gui/dist")?;
|
sh.remove_path("./web-client/iron-remote-desktop/dist")?;
|
||||||
|
sh.remove_path("./web-client/iron-remote-desktop-rdp/node_modules")?;
|
||||||
|
sh.remove_path("./web-client/iron-remote-desktop-rdp/dist")?;
|
||||||
sh.remove_path("./web-client/iron-svelte-client/node_modules")?;
|
sh.remove_path("./web-client/iron-svelte-client/node_modules")?;
|
||||||
println!("Done.");
|
println!("Done.");
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ TASKS:
|
||||||
wasm install Install dependencies required to build the WASM target
|
wasm install Install dependencies required to build the WASM target
|
||||||
web check Ensure Web Client is building without error
|
web check Ensure Web Client is building without error
|
||||||
web install Install dependencies required to build and run Web Client
|
web install Install dependencies required to build and run Web Client
|
||||||
|
web build Build the Web Client
|
||||||
web run Run SvelteKit-based standalone Web Client
|
web run Run SvelteKit-based standalone Web Client
|
||||||
ffi install Install all requirements for ffi tasks
|
ffi install Install all requirements for ffi tasks
|
||||||
ffi build [--release] Build DLL for FFI (default is debug)
|
ffi build [--release] Build DLL for FFI (default is debug)
|
||||||
|
@ -88,6 +89,7 @@ pub enum Action {
|
||||||
WasmInstall,
|
WasmInstall,
|
||||||
WebCheck,
|
WebCheck,
|
||||||
WebInstall,
|
WebInstall,
|
||||||
|
WebBuild,
|
||||||
WebRun,
|
WebRun,
|
||||||
FfiInstall,
|
FfiInstall,
|
||||||
FfiBuildDll {
|
FfiBuildDll {
|
||||||
|
@ -159,6 +161,7 @@ pub fn parse_args() -> anyhow::Result<Args> {
|
||||||
Some("web") => match args.subcommand()?.as_deref() {
|
Some("web") => match args.subcommand()?.as_deref() {
|
||||||
Some("check") => Action::WebCheck,
|
Some("check") => Action::WebCheck,
|
||||||
Some("install") => Action::WebInstall,
|
Some("install") => Action::WebInstall,
|
||||||
|
Some("build") => Action::WebBuild,
|
||||||
Some("run") => Action::WebRun,
|
Some("run") => Action::WebRun,
|
||||||
Some(unknown) => anyhow::bail!("unknown web action: {unknown}"),
|
Some(unknown) => anyhow::bail!("unknown web action: {unknown}"),
|
||||||
None => Action::ShowHelp,
|
None => Action::ShowHelp,
|
||||||
|
|
|
@ -110,6 +110,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
Action::WasmCheck => wasm::check(&sh)?,
|
Action::WasmCheck => wasm::check(&sh)?,
|
||||||
Action::WasmInstall => wasm::install(&sh)?,
|
Action::WasmInstall => wasm::install(&sh)?,
|
||||||
Action::WebCheck => web::check(&sh)?,
|
Action::WebCheck => web::check(&sh)?,
|
||||||
|
Action::WebBuild => web::build(&sh, false)?,
|
||||||
Action::WebInstall => web::install(&sh)?,
|
Action::WebInstall => web::install(&sh)?,
|
||||||
Action::WebRun => web::run(&sh)?,
|
Action::WebRun => web::run(&sh)?,
|
||||||
Action::FfiInstall => ffi::install(&sh)?,
|
Action::FfiInstall => ffi::install(&sh)?,
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
const IRON_REMOTE_GUI_PATH: &str = "./web-client/iron-remote-gui";
|
const IRON_REMOTE_DESKTOP_PATH: &str = "./web-client/iron-remote-desktop";
|
||||||
|
const IRON_REMOTE_DESKTOP_RDP_PATH: &str = "./web-client/iron-remote-desktop-rdp";
|
||||||
const IRON_SVELTE_CLIENT_PATH: &str = "./web-client/iron-svelte-client";
|
const IRON_SVELTE_CLIENT_PATH: &str = "./web-client/iron-svelte-client";
|
||||||
const IRONRDP_WEB_PATH: &str = "./crates/ironrdp-web";
|
const IRONRDP_WEB_PATH: &str = "./crates/ironrdp-web";
|
||||||
|
const IRONRDP_WEB_PACKAGE_JS_PATH: &str = "./crates/ironrdp-web/pkg/ironrdp_web.js";
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
const NPM: &str = "npm";
|
const NPM: &str = "npm";
|
||||||
|
@ -12,7 +15,8 @@ const NPM: &str = "npm.cmd";
|
||||||
pub fn install(sh: &Shell) -> anyhow::Result<()> {
|
pub fn install(sh: &Shell) -> anyhow::Result<()> {
|
||||||
let _s = Section::new("WEB-INSTALL");
|
let _s = Section::new("WEB-INSTALL");
|
||||||
|
|
||||||
run_cmd_in!(sh, IRON_REMOTE_GUI_PATH, "{NPM} install")?;
|
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_PATH, "{NPM} install")?;
|
||||||
|
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_RDP_PATH, "{NPM} install")?;
|
||||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} install")?;
|
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} install")?;
|
||||||
|
|
||||||
cargo_install(sh, &WASM_PACK)?;
|
cargo_install(sh, &WASM_PACK)?;
|
||||||
|
@ -25,8 +29,10 @@ pub fn check(sh: &Shell) -> anyhow::Result<()> {
|
||||||
|
|
||||||
build(sh, true)?;
|
build(sh, true)?;
|
||||||
|
|
||||||
run_cmd_in!(sh, IRON_REMOTE_GUI_PATH, "{NPM} run check")?;
|
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_PATH, "{NPM} run check")?;
|
||||||
run_cmd_in!(sh, IRON_REMOTE_GUI_PATH, "{NPM} run lint")?;
|
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_PATH, "{NPM} run lint")?;
|
||||||
|
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_RDP_PATH, "{NPM} run check")?;
|
||||||
|
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_RDP_PATH, "{NPM} run lint")?;
|
||||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run check")?;
|
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run check")?;
|
||||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run lint")?;
|
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run lint")?;
|
||||||
|
|
||||||
|
@ -36,22 +42,34 @@ pub fn check(sh: &Shell) -> anyhow::Result<()> {
|
||||||
pub fn run(sh: &Shell) -> anyhow::Result<()> {
|
pub fn run(sh: &Shell) -> anyhow::Result<()> {
|
||||||
let _s = Section::new("WEB-RUN");
|
let _s = Section::new("WEB-RUN");
|
||||||
|
|
||||||
build(sh, false)?;
|
|
||||||
|
|
||||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run dev-no-wasm")?;
|
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run dev-no-wasm")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(sh: &Shell, wasm_pack_dev: bool) -> anyhow::Result<()> {
|
pub fn build(sh: &Shell, wasm_pack_dev: bool) -> anyhow::Result<()> {
|
||||||
if wasm_pack_dev {
|
if wasm_pack_dev {
|
||||||
run_cmd_in!(sh, IRONRDP_WEB_PATH, "wasm-pack build --dev --target web")?;
|
run_cmd_in!(sh, IRONRDP_WEB_PATH, "wasm-pack build --dev --target web")?;
|
||||||
} else {
|
} else {
|
||||||
let _env_guard = sh.push_env("RUSTFLAGS", "-Ctarget-feature=+simd128");
|
let _env_guard = sh.push_env("RUSTFLAGS", "-Ctarget-feature=+simd128,+bulk-memory");
|
||||||
run_cmd_in!(sh, IRONRDP_WEB_PATH, "wasm-pack build --target web")?;
|
run_cmd_in!(sh, IRONRDP_WEB_PATH, "wasm-pack build --target web")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
run_cmd_in!(sh, IRON_REMOTE_GUI_PATH, "{NPM} run build-alone")?;
|
let ironrdp_web_js_file_path = sh.current_dir().join(IRONRDP_WEB_PACKAGE_JS_PATH);
|
||||||
|
|
||||||
|
let ironrdp_web_js_content = fs::read_to_string(&ironrdp_web_js_file_path)?;
|
||||||
|
|
||||||
|
// Modify the js file to get rid of the `URL` object.
|
||||||
|
// Vite doesn't work properly with inlined urls in `new URL(url, import.meta.url)`.
|
||||||
|
let ironrdp_web_js_content = format!(
|
||||||
|
"import wasmUrl from './ironrdp_web_bg.wasm?url';\n\n{}",
|
||||||
|
ironrdp_web_js_content
|
||||||
|
);
|
||||||
|
let ironrdp_web_js_content =
|
||||||
|
ironrdp_web_js_content.replace("new URL('ironrdp_web_bg.wasm', import.meta.url)", "wasmUrl");
|
||||||
|
|
||||||
|
fs::write(&ironrdp_web_js_file_path, ironrdp_web_js_content)?;
|
||||||
|
|
||||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run build-no-wasm")?;
|
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run build-no-wasm")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue