diff --git a/web-client/iron-remote-desktop-rdp/src/interfaces/Extension.ts b/web-client/iron-remote-desktop-rdp/src/interfaces/Extension.ts new file mode 100644 index 000000000..c912a2808 --- /dev/null +++ b/web-client/iron-remote-desktop-rdp/src/interfaces/Extension.ts @@ -0,0 +1,28 @@ +type ExtensionValue = { Pcb: string } | { KdcProxyUrl: string } | { DisplayControl: boolean }; + +export class Extension { + static init(ident: string, value: unknown): ExtensionValue { + switch (ident) { + case 'Pcb': + if (typeof value === 'string') { + return { Pcb: value }; + } else { + throw new Error('Pcb must be a string'); + } + case 'KdcProxyUrl': + if (typeof value === 'string') { + return { KdcProxyUrl: value }; + } else { + throw new Error('KdcProxyUrl must be a string'); + } + case 'DisplayControl': + if (typeof value === 'boolean') { + return { DisplayControl: value }; + } else { + throw new Error('DisplayControl must be a boolean'); + } + default: + throw new Error(`Invalid extension type: ${ident}`); + } + } +} diff --git a/web-client/iron-remote-desktop-rdp/src/main.ts b/web-client/iron-remote-desktop-rdp/src/main.ts index 5254242d9..feab35f48 100644 --- a/web-client/iron-remote-desktop-rdp/src/main.ts +++ b/web-client/iron-remote-desktop-rdp/src/main.ts @@ -10,6 +10,7 @@ import init, { ClipboardTransaction, ClipboardContent, } from '../../../crates/ironrdp-web/pkg/ironrdp_web'; +import { Extension } from './interfaces/Extension'; export default { init, @@ -23,4 +24,5 @@ export default { ClipboardContent, Session, SessionTerminationInfo, + Extension, }; diff --git a/web-client/iron-remote-desktop/src/interfaces/Extension.ts b/web-client/iron-remote-desktop/src/interfaces/Extension.ts new file mode 100644 index 000000000..7c792c15c --- /dev/null +++ b/web-client/iron-remote-desktop/src/interfaces/Extension.ts @@ -0,0 +1,5 @@ +import type { ExtensionValue } from './ExtensionValue'; + +export interface Extension { + init(ident: string, value: unknown): ExtensionValue; +} diff --git a/web-client/iron-remote-desktop/src/interfaces/ExtensionValue.ts b/web-client/iron-remote-desktop/src/interfaces/ExtensionValue.ts new file mode 100644 index 000000000..a43a26eba --- /dev/null +++ b/web-client/iron-remote-desktop/src/interfaces/ExtensionValue.ts @@ -0,0 +1 @@ +export type ExtensionValue = unknown; diff --git a/web-client/iron-remote-desktop/src/interfaces/RemoteDesktopModule.ts b/web-client/iron-remote-desktop/src/interfaces/RemoteDesktopModule.ts index 48151b70d..c12bddca3 100644 --- a/web-client/iron-remote-desktop/src/interfaces/RemoteDesktopModule.ts +++ b/web-client/iron-remote-desktop/src/interfaces/RemoteDesktopModule.ts @@ -7,6 +7,7 @@ import type { SessionBuilder } from './SessionBuilder'; import type { SessionTerminationInfo } from './SessionTerminationInfo'; import type { ClipboardTransaction } from './ClipboardTransaction'; import type { ClipboardContent } from './ClipboardContent'; +import type { Extension } from './Extension'; export interface RemoteDesktopModule { init: () => Promise; @@ -20,4 +21,5 @@ export interface RemoteDesktopModule { SessionTerminationInfo: SessionTerminationInfo; ClipboardTransaction: ClipboardTransaction; ClipboardContent: ClipboardContent; + Extension: Extension; } diff --git a/web-client/iron-remote-desktop/src/interfaces/UserInteraction.ts b/web-client/iron-remote-desktop/src/interfaces/UserInteraction.ts index fc87de88c..6ba022b3e 100644 --- a/web-client/iron-remote-desktop/src/interfaces/UserInteraction.ts +++ b/web-client/iron-remote-desktop/src/interfaces/UserInteraction.ts @@ -1,25 +1,17 @@ import type { ScreenScale } from '../enums/ScreenScale'; import type { NewSessionInfo } from './NewSessionInfo'; import type { SessionEvent } from './session-event'; -import type { DesktopSize } from './DesktopSize'; +import { ConfigBuilder } from '../services/ConfigBuilder'; +import type { Config } from '../services/Config'; export interface UserInteraction { setVisibility(state: boolean): void; setScale(scale: ScreenScale): void; - connect( - username: string, - password: string, - destination: string, - proxyAddress: string, - serverDomain: string, - authToken: string, - desktopSize?: DesktopSize, - preConnectionBlob?: string, - kdc_proxy_url?: string, - use_display_control?: boolean, - ): Promise; + configBuilder(): ConfigBuilder; + + connect(config: Config): Promise; setKeyboardUnicodeMode(use_unicode: boolean): void; diff --git a/web-client/iron-remote-desktop/src/services/Config.ts b/web-client/iron-remote-desktop/src/services/Config.ts new file mode 100644 index 000000000..b3adf647b --- /dev/null +++ b/web-client/iron-remote-desktop/src/services/Config.ts @@ -0,0 +1,33 @@ +import type { DesktopSize } from '../interfaces/DesktopSize'; +import type { ExtensionValue } from '../interfaces/ExtensionValue'; + +export class Config { + readonly username: string; + readonly password: string; + readonly destination: string; + readonly proxyAddress: string; + readonly serverDomain: string; + readonly authToken: string; + readonly desktopSize?: DesktopSize; + readonly extensions: ExtensionValue[]; + + constructor( + userData: { username: string; password: string }, + proxyData: { address: string; authToken: string }, + configOptions: { + destination: string; + serverDomain: string; + extensions: ExtensionValue[]; + desktopSize?: DesktopSize; + }, + ) { + this.username = userData.username; + this.password = userData.password; + this.proxyAddress = proxyData.address; + this.authToken = proxyData.authToken; + this.destination = configOptions.destination; + this.serverDomain = configOptions.serverDomain; + this.extensions = configOptions.extensions; + this.desktopSize = configOptions.desktopSize; + } +} diff --git a/web-client/iron-remote-desktop/src/services/ConfigBuilder.ts b/web-client/iron-remote-desktop/src/services/ConfigBuilder.ts new file mode 100644 index 000000000..ad291ac51 --- /dev/null +++ b/web-client/iron-remote-desktop/src/services/ConfigBuilder.ts @@ -0,0 +1,160 @@ +import type { DesktopSize } from '../interfaces/DesktopSize'; +import { Config } from './Config'; +import type { ExtensionValue } from '../interfaces/ExtensionValue'; + +type ExtensionConstructor = (ident: string, value: unknown) => ExtensionValue; + +/** + * Builder class for creating Config objects with a fluent interface. + * + * @example + * ```typescript + * const configBuilder = new ConfigBuilder(createExtensionFunction); + * const config = configBuilder + * .withDestination(destination) + * .withProxyAddress(proxyAddress) + * .withAuthToken(authToken) + * ... + * .build(); + * ``` + */ +export class ConfigBuilder { + private extensionConstructor: ExtensionConstructor; + + private username: string = ''; + private password: string = ''; + private destination: string = ''; + private proxyAddress: string = ''; + private serverDomain: string = ''; + private authToken: string = ''; + private desktopSize?: DesktopSize; + + private extensions: ExtensionValue[] = []; + + /** + * Creates a new ConfigBuilder instance. + * + * @param extensionConstructor - Function that creates extension values from identifiers and values. + */ + constructor(extensionConstructor: ExtensionConstructor) { + this.extensionConstructor = extensionConstructor; + } + + /** + * Optional parameter + * + * @param username - The username to use for authentication + * @returns The builder instance for method chaining + */ + withUsername(username: string): ConfigBuilder { + this.username = username; + return this; + } + + /** + * Optional parameter + * + * @param password - The password for authentication + * @returns The builder instance for method chaining + */ + withPassword(password: string): ConfigBuilder { + this.password = password; + return this; + } + + /** + * Required parameter + * + * @param destination - The destination address to connect to + * @returns The builder instance for method chaining + */ + withDestination(destination: string): ConfigBuilder { + this.destination = destination; + return this; + } + + /** + * Required parameter + * + * @param proxyAddress - The address of the proxy server + * @returns The builder instance for method chaining + */ + withProxyAddress(proxyAddress: string): ConfigBuilder { + this.proxyAddress = proxyAddress; + return this; + } + + /** + * Optional parameter + * + * @param serverDomain - The server domain to connect to + * @returns The builder instance for method chaining + */ + withServerDomain(serverDomain: string): ConfigBuilder { + this.serverDomain = serverDomain; + return this; + } + + /** + * Required parameter + * + * @param authToken - JWT token to connect to the proxy + * @returns The builder instance for method chaining + */ + withAuthToken(authToken: string): ConfigBuilder { + this.authToken = authToken; + return this; + } + + /** + * Optional parameter + * + * @param ident - The identifier for the extension + * @param value - The value for the extension + * @returns The builder instance for method chaining + */ + withExtension(ident: string, value: unknown): ConfigBuilder { + this.extensions.push(this.extensionConstructor(ident, value)); + return this; + } + + /** + * Optional + * + * @param desktopSize - The desktop size configuration object + * @returns The builder instance for method chaining + */ + withDesktopSize(desktopSize: DesktopSize): ConfigBuilder { + this.desktopSize = desktopSize; + return this; + } + + /** + * Builds a new Config instance. + * + * @throws {Error} If required parameters (destination, proxyAddress, authToken) are not set + * @returns A new Config instance with the configured values + */ + build(): Config { + if (this.destination === '') { + throw new Error('destination has to be specified'); + } + if (this.proxyAddress === '') { + throw new Error('proxy address has to be specified'); + } + if (this.authToken === '') { + throw new Error('authentication token has to be specified'); + } + const userData = { username: this.username, password: this.password }; + const proxyData = { address: this.proxyAddress, authToken: this.authToken }; + + const configOptions = { + destination: this.destination, + serverDomain: this.serverDomain, + extensions: this.extensions, + desktopSize: this.desktopSize, + }; + + return new Config(userData, proxyData, configOptions); + } +} diff --git a/web-client/iron-remote-desktop/src/services/PublicAPI.ts b/web-client/iron-remote-desktop/src/services/PublicAPI.ts index 335930ba0..909cb88b9 100644 --- a/web-client/iron-remote-desktop/src/services/PublicAPI.ts +++ b/web-client/iron-remote-desktop/src/services/PublicAPI.ts @@ -4,7 +4,8 @@ import { SpecialCombination } from '../enums/SpecialCombination'; import { RemoteDesktopService } from './remote-desktop.service'; import type { UserInteraction } from '../interfaces/UserInteraction'; import type { ScreenScale } from '../enums/ScreenScale'; -import type { DesktopSize } from '../interfaces/DesktopSize'; +import { ConfigBuilder } from './ConfigBuilder'; +import { Config } from './Config'; export class PublicAPI { private remoteDesktopService: RemoteDesktopService; @@ -13,31 +14,13 @@ export class PublicAPI { this.remoteDesktopService = remoteDesktopService; } - private connect( - username: string, - password: string, - destination: string, - proxyAddress: string, - serverDomain: string, - authToken: string, - desktopSize?: DesktopSize, - preConnectionBlob?: string, - kdc_proxy_url?: string, - use_display_control = false, - ): Promise { + private configBuilder(): ConfigBuilder { + return this.remoteDesktopService.configBuilder(); + } + + private connect(config: Config): Promise { loggingService.info('Initializing connection.'); - const resultObservable = this.remoteDesktopService.connect( - username, - password, - destination, - proxyAddress, - serverDomain, - authToken, - desktopSize, - preConnectionBlob, - kdc_proxy_url, - use_display_control, - ); + const resultObservable = this.remoteDesktopService.connect(config); return resultObservable.toPromise(); } @@ -82,6 +65,7 @@ export class PublicAPI { getExposedFunctions(): UserInteraction { return { setVisibility: this.setVisibility.bind(this), + configBuilder: this.configBuilder.bind(this), connect: this.connect.bind(this), setScale: this.setScale.bind(this), onSessionEvent: (callback) => { diff --git a/web-client/iron-remote-desktop/src/services/remote-desktop.service.ts b/web-client/iron-remote-desktop/src/services/remote-desktop.service.ts index e63a5166b..c4e451725 100644 --- a/web-client/iron-remote-desktop/src/services/remote-desktop.service.ts +++ b/web-client/iron-remote-desktop/src/services/remote-desktop.service.ts @@ -13,13 +13,14 @@ 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 { 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'; +import { ConfigBuilder } from './ConfigBuilder'; +import type { Config } from './Config'; type OnRemoteClipboardChanged = (transaction: ClipboardTransaction) => void; type OnRemoteReceivedFormatsList = () => void; @@ -135,37 +136,27 @@ export class RemoteDesktopService { this.mousePosition.next(position); } - connect( - username: string, - password: string, - destination: string, - proxyAddress: string, - serverDomain: string, - authToken: string, - desktopSize?: DesktopSize, - preConnectionBlob?: string, - kdc_proxy_url?: string, - use_display_control = true, - ): Observable { + configBuilder(): ConfigBuilder { + return new ConfigBuilder(this.module.Extension.init); + } + + connect(config: Config): Observable { const sessionBuilder = this.module.SessionBuilder.init(); - sessionBuilder.proxy_address(proxyAddress); - sessionBuilder.destination(destination); - sessionBuilder.server_domain(serverDomain); - sessionBuilder.password(password); - sessionBuilder.auth_token(authToken); - sessionBuilder.username(username); + sessionBuilder.proxy_address(config.proxyAddress); + sessionBuilder.destination(config.destination); + sessionBuilder.server_domain(config.serverDomain); + sessionBuilder.password(config.password); + sessionBuilder.auth_token(config.authToken); + sessionBuilder.username(config.username); sessionBuilder.render_canvas(this.canvas!); sessionBuilder.set_cursor_style_callback_context(this); sessionBuilder.set_cursor_style_callback(this.setCursorStyleCallback); - sessionBuilder.extension({ DisplayControl: use_display_control }); - if (preConnectionBlob != null) { - sessionBuilder.extension({ Pcb: preConnectionBlob }); - } - if (kdc_proxy_url != null) { - sessionBuilder.extension({ KdcProxyUrl: kdc_proxy_url }); - } + config.extensions.forEach((extension) => { + sessionBuilder.extension(extension); + }); + if (this.onRemoteClipboardChanged != null && this.enableClipboard) { sessionBuilder.remote_clipboard_changed_callback(this.onRemoteClipboardChanged); } @@ -176,8 +167,10 @@ export class RemoteDesktopService { sessionBuilder.force_clipboard_update_callback(this.onForceClipboardUpdate); } - if (desktopSize != null) { - sessionBuilder.desktop_size(this.module.DesktopSize.init(desktopSize.width, desktopSize.height)); + if (config.desktopSize != null) { + sessionBuilder.desktop_size( + this.module.DesktopSize.init(config.desktopSize.width, config.desktopSize.height), + ); } // Type guard to filter out errors diff --git a/web-client/iron-svelte-client/src/lib/login/login.svelte b/web-client/iron-svelte-client/src/lib/login/login.svelte index 949413389..e2c49ac1b 100644 --- a/web-client/iron-svelte-client/src/lib/login/login.svelte +++ b/web-client/iron-svelte-client/src/lib/login/login.svelte @@ -15,7 +15,7 @@ let authtoken = ''; let kdc_proxy_url = ''; let desktopSize = new DesktopSize(1280, 768); - let pcb: string; + let pcb = ''; let pop_up = false; let enable_clipboard = true; @@ -115,20 +115,28 @@ } userInteraction.setEnableClipboard(enable_clipboard); - from( - userInteraction.connect( - username, - password, - hostname, - gatewayAddress, - domain, - authtoken, - desktopSize, - pcb, - kdc_proxy_url, - true, - ), - ) + + const configBuilder = userInteraction + .configBuilder() + .withUsername(username) + .withPassword(password) + .withDestination(hostname) + .withProxyAddress(gatewayAddress) + .withServerDomain(domain) + .withAuthToken(authtoken) + .withDesktopSize(desktopSize) + .withExtension('DisplayControl', true); + + if (pcb !== '') { + configBuilder.withExtension('Pcb', pcb); + } + + if (kdc_proxy_url !== '') { + configBuilder.withExtension('KdcProxyUrl', kdc_proxy_url); + } + const config = configBuilder.build(); + + from(userInteraction.connect(config)) .pipe( catchError((err) => { toast.set({ diff --git a/web-client/iron-svelte-client/src/lib/popup-screen/popup-screen.svelte b/web-client/iron-svelte-client/src/lib/popup-screen/popup-screen.svelte index 70aacf74f..aa016a73d 100644 --- a/web-client/iron-svelte-client/src/lib/popup-screen/popup-screen.svelte +++ b/web-client/iron-svelte-client/src/lib/popup-screen/popup-screen.svelte @@ -35,23 +35,31 @@ const parsedData = JSON.parse(atob(data)); const { hostname, gatewayAddress, domain, username, password, authtoken, kdc_proxy_url, pcb, desktopSize } = parsedData; - uiService - .connect( - username, - password, - hostname, - gatewayAddress, - domain, - authtoken, - desktopSize, - pcb, - kdc_proxy_url, - true, - ) - .then(() => { - uiService.setVisibility(true); - window.onresize = onWindowResize; - }); + + const configBuilder = uiService + .configBuilder() + .withUsername(username) + .withPassword(password) + .withDestination(hostname) + .withProxyAddress(gatewayAddress) + .withServerDomain(domain) + .withAuthToken(authtoken) + .withDesktopSize(desktopSize) + .withExtension('DisplayControl', true); + + if (pcb !== '') { + configBuilder.withExtension('Pcb', pcb); + } + + if (kdc_proxy_url !== '') { + configBuilder.withExtension('KdcProxyUrl', kdc_proxy_url); + } + const config = configBuilder.build(); + + uiService.connect(config).then(() => { + uiService.setVisibility(true); + window.onresize = onWindowResize; + }); } });