fix(web-client)!: remove dependency on RxJS (#818)

This commit is contained in:
Alex Yusiuk 2025-06-11 17:10:02 +03:00 committed by GitHub
parent 112a1672d5
commit 03f793940a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 164 additions and 238 deletions

View file

@ -8,7 +8,6 @@
"name": "@devolutions/iron-remote-desktop-rdp",
"version": "0.0.0",
"dependencies": {
"rxjs": "^6.6.7",
"ua-parser-js": "^1.0.33"
},
"devDependencies": {
@ -3525,24 +3524,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "6.6.7",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^1.9.0"
},
"engines": {
"npm": ">=2.0.0"
}
},
"node_modules/rxjs/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",

View file

@ -41,7 +41,6 @@
"vite-plugin-wasm": "^3.1.0"
},
"dependencies": {
"rxjs": "^6.6.7",
"ua-parser-js": "^1.0.33"
}
}

View file

@ -12,8 +12,5 @@
"files": [
"iron-remote-desktop-rdp.js",
"index.d.ts"
],
"dependencies": {
"rxjs": "^6.6.7"
}
]
}

View file

@ -8,7 +8,6 @@
"name": "@devolutions/iron-remote-desktop",
"version": "0.0.0",
"dependencies": {
"rxjs": "^6.6.7",
"svelte-eslint-parser": "^1.0.0",
"ua-parser-js": "^1.0.33"
},
@ -3959,24 +3958,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "6.6.7",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^1.9.0"
},
"engines": {
"npm": ">=2.0.0"
}
},
"node_modules/rxjs/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",

View file

@ -49,7 +49,6 @@
"vite-plugin-wasm": "^3.1.0"
},
"dependencies": {
"rxjs": "^6.6.7",
"svelte-eslint-parser": "^1.0.0",
"ua-parser-js": "^1.0.33"
}

View file

@ -16,8 +16,5 @@
"files": [
"iron-remote-desktop.js",
"index.d.ts"
],
"dependencies": {
"rxjs": "^6.6.7"
}
]
}

View file

@ -3,8 +3,8 @@ import type { NewSessionInfo } from './NewSessionInfo';
import type { SessionEvent } from './session-event';
import { ConfigBuilder } from '../services/ConfigBuilder';
import type { Config } from '../services/Config';
import type { PartialObserver } from 'rxjs';
import type { Extension } from './Extension';
import type { Callback } from '../lib/Observable';
export interface UserInteraction {
setVisibility(state: boolean): void;
@ -25,7 +25,7 @@ export interface UserInteraction {
setCursorStyleOverride(style: string | null): void;
onSessionEvent(partialObserver: PartialObserver<SessionEvent>): void;
onSessionEvent(callback: Callback<SessionEvent>): void;
resize(width: number, height: number, scale?: number): void;

View file

@ -456,7 +456,7 @@
}
function serverBridgeListeners() {
remoteDesktopService.resize.subscribe((evt: ResizeEvent) => {
remoteDesktopService.resizeObservable.subscribe((evt: ResizeEvent) => {
loggingService.info(`Resize canvas to: ${evt.desktopSize.width}x${evt.desktopSize.height}`);
canvas.width = evt.desktopSize.width;
canvas.height = evt.desktopSize.height;
@ -469,12 +469,12 @@
scaleSession(scale);
});
remoteDesktopService.scaleObserver.subscribe((s) => {
remoteDesktopService.scaleObservable.subscribe((s) => {
loggingService.info('Change scale!');
scaleSession(s);
});
remoteDesktopService.dynamicResize.subscribe((evt) => {
remoteDesktopService.dynamicResizeObservable.subscribe((evt) => {
loggingService.info(`Dynamic resize!, width: ${evt.width}, height: ${evt.height}`);
setViewerStyle(evt.height.toString(), evt.width.toString(), true);
});

View file

@ -0,0 +1,19 @@
export type Callback<T> = (_: T) => void;
export class Observable<T> {
constructor() {
this.subscribers = [];
}
subscribers: Array<Callback<T>>;
subscribe(cb: Callback<T>) {
this.subscribers.push(cb);
}
publish(value: T) {
for (const cb of this.subscribers) {
cb(value);
}
}
}

View file

@ -21,9 +21,7 @@ export class PublicAPI {
private connect(config: Config): Promise<NewSessionInfo> {
loggingService.info('Initializing connection.');
const resultObservable = this.remoteDesktopService.connect(config);
return resultObservable.toPromise();
return this.remoteDesktopService.connect(config);
}
private ctrlAltDel() {
@ -73,8 +71,8 @@ export class PublicAPI {
configBuilder: this.configBuilder.bind(this),
connect: this.connect.bind(this),
setScale: this.setScale.bind(this),
onSessionEvent: (partialObserver) => {
this.remoteDesktopService.sessionObserver.subscribe(partialObserver);
onSessionEvent: (callback) => {
this.remoteDesktopService.sessionEventObservable.subscribe(callback);
},
ctrlAltDel: this.ctrlAltDel.bind(this),
metaKey: this.metaKey.bind(this),

View file

@ -1,6 +1,4 @@
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
import { loggingService } from './logging.service';
import { catchError, filter, map } from 'rxjs/operators';
import { scanCode } from '../lib/scancodes';
import { OS } from '../enums/OS';
import { ModifierKey } from '../enums/ModifierKey';
@ -11,30 +9,28 @@ import { SpecialCombination } from '../enums/SpecialCombination';
import type { ResizeEvent } from '../interfaces/ResizeEvent';
import { ScreenScale } from '../enums/ScreenScale';
import type { MousePosition } from '../interfaces/MousePosition';
import type { SessionEvent, IronErrorKind, IronError } from '../interfaces/session-event';
import type { IronError, IronErrorKind, SessionEvent } from '../interfaces/session-event';
import type { ClipboardData } from '../interfaces/ClipboardData';
import type { Session } from '../interfaces/Session';
import type { DeviceEvent } from '../interfaces/DeviceEvent';
import type { SessionTerminationInfo } from '../interfaces/SessionTerminationInfo';
import type { RemoteDesktopModule } from '../interfaces/RemoteDesktopModule';
import { ConfigBuilder } from './ConfigBuilder';
import type { Config } from './Config';
import type { Extension } from '../interfaces/Extension';
import { Observable } from '../lib/Observable';
type OnRemoteClipboardChanged = (data: ClipboardData) => void;
type OnRemoteReceivedFormatsList = () => void;
type OnForceClipboardUpdate = () => void;
const isIronError = (error: unknown): error is IronError =>
typeof error === 'object' &&
error !== null &&
typeof (error as IronError).backtrace === 'function' &&
typeof (error as IronError).kind === 'function';
export class RemoteDesktopService {
private module: RemoteDesktopModule;
private _resize: Subject<ResizeEvent> = new Subject<ResizeEvent>();
private mousePosition: BehaviorSubject<MousePosition> = new BehaviorSubject<MousePosition>({
x: 0,
y: 0,
});
private changeVisibility: Subject<boolean> = new Subject();
private sessionEvent: Subject<SessionEvent> = new Subject();
private scale: BehaviorSubject<ScreenScale> = new BehaviorSubject(ScreenScale.Fit as ScreenScale);
private canvas?: HTMLCanvasElement;
private keyboardUnicodeMode: boolean = false;
private backendSupportsUnicodeKeyboardShortcuts: boolean | undefined = undefined;
@ -45,21 +41,19 @@ export class RemoteDesktopService {
private lastCursorStyle: string = 'default';
private enableClipboard: boolean = true;
resize: Observable<ResizeEvent>;
resizeObservable: Observable<ResizeEvent> = new Observable();
session?: Session;
modifierKeyPressed: ModifierKey[] = [];
mousePositionObservable: Observable<MousePosition> = this.mousePosition.asObservable();
changeVisibilityObservable: Observable<boolean> = this.changeVisibility.asObservable();
sessionObserver: Observable<SessionEvent> = this.sessionEvent.asObservable();
scaleObserver: Observable<ScreenScale> = this.scale.asObservable();
dynamicResize = new Subject<{
width: number;
height: number;
}>();
mousePositionObservable: Observable<MousePosition> = new Observable();
changeVisibilityObservable: Observable<boolean> = new Observable();
sessionEventObservable: Observable<SessionEvent> = new Observable();
scaleObservable: Observable<ScreenScale> = new Observable();
dynamicResizeObservable: Observable<{ width: number; height: number }> = new Observable();
constructor(module: RemoteDesktopModule) {
this.resize = this._resize.asObservable();
this.module = module;
loggingService.info('Web bridge initialized.');
}
@ -113,14 +107,14 @@ export class RemoteDesktopService {
updateMousePosition(position: MousePosition) {
this.doTransactionFromDeviceEvents([this.module.DeviceEvent.mouseMove(position.x, position.y)]);
this.mousePosition.next(position);
this.mousePositionObservable.publish(position);
}
configBuilder(): ConfigBuilder {
return new ConfigBuilder();
}
connect(config: Config): Observable<NewSessionInfo> {
async connect(config: Config): Promise<NewSessionInfo> {
const sessionBuilder = new this.module.SessionBuilder();
sessionBuilder.proxyAddress(config.proxyAddress);
@ -153,68 +147,66 @@ export class RemoteDesktopService {
);
}
// Type guard to filter out errors
function isSession(result: IronError | Session): result is Session {
// Check whether function exists. To make it more robust we can check every method.
return (<Session>result).run !== undefined;
}
const session = await sessionBuilder.connect().catch((err: IronError) => {
this.raiseSessionEvent({
type: SessionEventType.ERROR,
data: {
backtrace: () => err.backtrace(),
kind: () => err.kind() as number as IronErrorKind,
},
});
throw new Error('could not connect to the session');
});
await this.run(session);
loggingService.info('Session started.');
this.session = session;
this.resizeObservable.publish({
desktopSize: session.desktopSize(),
sessionId: 0,
});
this.raiseSessionEvent({
type: SessionEventType.STARTED,
data: 'Session started',
});
return {
sessionId: 0,
initialDesktopSize: session.desktopSize(),
websocketPort: 0,
};
}
async run(session: Session): Promise<Session> {
try {
const termination_info = await session.run();
this.setVisibility(false);
this.raiseSessionEvent({
type: SessionEventType.TERMINATED,
data: 'Session was terminated: ' + termination_info.reason() + '.',
});
return session;
} catch (err) {
if (isIronError(err)) {
this.setVisibility(false);
return from(sessionBuilder.connect()).pipe(
catchError((err: IronError) => {
this.raiseSessionEvent({
type: SessionEventType.ERROR,
data: {
backtrace: () => err.backtrace(),
kind: () => err.kind() as number as IronErrorKind,
},
});
return of(err);
}),
filter(isSession),
map((session: Session) => {
from(session.run())
.pipe(
catchError((err: IronError) => {
this.setVisibility(false);
this.raiseSessionEvent({
type: SessionEventType.ERROR,
data: err.backtrace(),
});
this.raiseSessionEvent({
type: SessionEventType.TERMINATED,
data: 'Session was terminated.',
});
throw err;
}),
map((termination_info: SessionTerminationInfo) => {
this.setVisibility(false);
this.raiseSessionEvent({
type: SessionEventType.TERMINATED,
data: 'Session was terminated: ' + termination_info.reason() + '.',
});
}),
)
.subscribe();
return session;
}),
map((session: Session) => {
loggingService.info('Session started.');
this.session = session;
this._resize.next({
desktopSize: session.desktopSize(),
sessionId: 0,
data: err.backtrace(),
});
this.raiseSessionEvent({
type: SessionEventType.STARTED,
data: 'Session started',
type: SessionEventType.TERMINATED,
data: 'Session was terminated.',
});
return {
sessionId: 0,
initialDesktopSize: session.desktopSize(),
websocketPort: 0,
};
}),
);
}
throw new Error('could not run the session.');
}
}
sendSpecialCombination(specialCombination: SpecialCombination): void {
@ -235,11 +227,11 @@ export class RemoteDesktopService {
}
setVisibility(state: boolean) {
this.changeVisibility.next(state);
this.changeVisibilityObservable.publish(state);
}
setScale(scale: ScreenScale) {
this.scale.next(scale);
this.scaleObservable.publish(scale);
}
setCanvas(canvas: HTMLCanvasElement) {
@ -247,7 +239,7 @@ export class RemoteDesktopService {
}
resizeDynamic(width: number, height: number, scale?: number) {
this.dynamicResize.next({ width, height });
this.dynamicResizeObservable.publish({ width, height });
this.session?.resize(width, height, scale);
}
@ -432,7 +424,7 @@ export class RemoteDesktopService {
}
private raiseSessionEvent(event: SessionEvent) {
this.sessionEvent.next(event);
this.sessionEventObservable.publish(event);
}
private updateModifierKeyState(evt: KeyboardEvent) {

View file

@ -26,7 +26,6 @@
"guid-typescript": "^1.0.9",
"prettier": "^3.1.0",
"prettier-plugin-svelte": "^3.1.0",
"rxjs": "^6.6.7",
"svelte": "^3.44.0",
"svelte-check": "^2.7.1",
"svelte-preprocess": "^4.10.6",
@ -3516,24 +3515,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "6.6.7",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
"dev": true,
"dependencies": {
"tslib": "^1.9.0"
},
"engines": {
"npm": ">=2.0.0"
}
},
"node_modules/rxjs/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",

View file

@ -32,7 +32,6 @@
"prettier": "^3.1.0",
"prettier-plugin-svelte": "^3.1.0",
"@rollup/plugin-replace": "^5.0.1",
"rxjs": "^6.6.7",
"svelte": "^3.44.0",
"svelte-check": "^2.7.1",
"@sveltejs/adapter-auto": "^2.0.0",

View file

@ -1,9 +1,8 @@
<script lang="ts">
import { currentSession, userInteractionService } from '../../services/session.service';
import { catchError, filter } from 'rxjs/operators';
import type { UserInteraction, NewSessionInfo } from '../../../static/iron-remote-desktop';
import type { UserInteraction } from '../../../static/iron-remote-desktop';
import type { Session } from '../../models/session';
import { preConnectionBlob, displayControl, kdcProxyUrl, init } from '../../../static/iron-remote-desktop-rdp';
import { from, of } from 'rxjs';
import { toast } from '$lib/messages/message-store';
import { showLogin } from '$lib/login/login-store';
import { onMount } from 'svelte';
@ -22,6 +21,24 @@
let userInteraction: UserInteraction;
const initListeners = () => {
userInteraction.onSessionEvent((event) => {
if (event.type === 2) {
console.log('Error event', event.data);
toast.set({
type: 'error',
message: typeof event.data !== 'string' ? event.data.backtrace() : event.data,
});
} else {
toast.set({
type: 'info',
message: typeof event.data === 'string' ? event.data : event.data?.backtrace() ?? 'No info',
});
}
});
};
userInteractionService.subscribe((val) => {
userInteraction = val;
if (val != null) {
@ -29,26 +46,6 @@
}
});
const initListeners = () => {
userInteraction.onSessionEvent({
next: (event) => {
if (event.type === 2) {
console.log('Error event', event.data);
toast.set({
type: 'error',
message: typeof event.data !== 'string' ? event.data.backtrace() : event.data,
});
} else {
toast.set({
type: 'info',
message: typeof event.data === 'string' ? event.data : event.data?.backtrace() ?? 'No info',
});
}
},
});
};
const StartSession = async () => {
if (authtoken === '') {
const token_server_url = import.meta.env.VITE_IRON_TOKEN_SERVER_URL as string | undefined;
@ -140,38 +137,27 @@
const config = configBuilder.build();
from(userInteraction.connect(config))
.pipe(
catchError((err) => {
toast.set({
type: 'info',
message: err.backtrace(),
});
return of(null);
}),
filter((result) => result !== null && result !== undefined), // Explicitly checking for null/undefined
)
.subscribe((info: NewSessionInfo | null) => {
if (info != null && info.initialDesktopSize !== null) {
toast.set({
type: 'info',
message: 'Success',
});
currentSession.update((session) =>
Object.assign(session, {
sessionId: info.sessionId,
desktopSize: info.initialDesktopSize,
active: true,
}),
);
showLogin.set(false);
} else {
toast.set({
type: 'error',
message: 'Failure',
});
}
try {
const session_info = await userInteraction.connect(config);
toast.set({
type: 'info',
message: 'Success',
});
const updater = (session: Session): Session => ({
...session,
sessionId: session_info.sessionId,
desktopSize: session_info.initialDesktopSize,
active: true,
});
currentSession.update(updater);
showLogin.set(false);
} catch (err) {
console.error(`Error occurred: ${err}`);
}
};
onMount(async () => {

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { setCurrentSessionActive, userInteractionService } from '../../services/session.service';
import type { UserInteraction } from '../../../static/iron-remote-desktop';
import type { UserInteraction, SessionEvent } from '../../../static/iron-remote-desktop';
import { Backend } from '../../../static/iron-remote-desktop-rdp';
import { preConnectionBlob, displayControl, kdcProxyUrl } from '../../../static/iron-remote-desktop-rdp';
@ -9,18 +9,17 @@
let cursorOverrideActive = false;
let showUtilityBar = false;
userInteractionService.subscribe((val) => {
if (val != null) {
userInteraction = val;
userInteraction.onSessionEvent({
next: (event) => {
if (event.type === 0) {
userInteraction.setVisibility(true);
} else if (event.type === 1) {
setCurrentSessionActive(false);
}
},
});
userInteractionService.subscribe((userInteraction) => {
if (userInteraction != null) {
const callback = (event: SessionEvent) => {
if (event.type === 0) {
userInteraction.setVisibility(true);
} else if (event.type === 1) {
setCurrentSessionActive(false);
}
};
userInteraction.onSessionEvent(callback);
}
});

View file

@ -12,15 +12,13 @@
userInteractionService.subscribe((uis) => {
if (uis != null) {
uiService = uis;
uiService.onSessionEvent({
next: (event) => {
if (event.type === 0) {
uiService.setVisibility(true);
} else if (event.type === 1) {
setCurrentSessionActive(false);
showLogin.set(true);
}
},
uiService.onSessionEvent((event) => {
if (event.type === 0) {
uiService.setVisibility(true);
} else if (event.type === 1) {
setCurrentSessionActive(false);
showLogin.set(true);
}
});
}
});