1
0
mirror of https://github.com/dkmstr/openuds-gui.git synced 2024-10-26 08:55:23 +03:00

Refactorized plugin launcher, Now we can understand the code :)

This commit is contained in:
Adolfo Gómez García 2023-01-12 18:58:33 +01:00
parent 241f2a943d
commit fef8c6ee0e
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
7 changed files with 284 additions and 268 deletions

View File

@ -21,6 +21,6 @@
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>Cancel</button>
<button mat-button [mat-dialog-close]="{'username': username, 'domain': domain, 'password': password}">Accept</button>
<button mat-button [mat-dialog-close]="{'username': username, 'domain': domain, 'password': password, 'success': false}">Cancel</button>
<button mat-button [mat-dialog-close]="{'username': username, 'domain': domain, 'password': password, 'success': true}">Accept</button>
</mat-dialog-actions>

View File

@ -1,22 +1,31 @@
import { Injectable } from '@angular/core';
import { timeout } from 'rxjs/operators';
import { ModalComponent, DialogType } from './modal/modal.component';
import { CredentialsModalComponent } from './credentials-modal/credentials-modal.component';
import { MatDialog } from '@angular/material/dialog';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Observable, firstValueFrom } from 'rxjs';
const toPromise = <T>(observable: Observable<T>): Promise<T> => firstValueFrom(observable);
export const toPromise = <T>(observable: Observable<T>|Promise<T>, wait?: number): Promise<T> => {
if (observable instanceof Promise) {
return observable;
}
if (wait) {
return firstValueFrom(observable.pipe(timeout(wait)));
}
return firstValueFrom(observable);
};
@Injectable()
export class UDSGuiService {
constructor(public dialog: MatDialog) {}
alert(
async alert(
title: string,
message: string,
autoclose = 0,
checkClose: Promise<boolean> = null
) {
): Promise<MatDialogRef<ModalComponent>> {
const width = window.innerWidth < 800 ? '80%' : '40%';
const dialogRef = this.dialog.open(ModalComponent, {
width,
@ -43,7 +52,7 @@ export class UDSGuiService {
return dialogRef.componentInstance.yesno;
}
askCredentials(username: string, domain: string): Promise<{username: string; password: string; domain: string}> {
askCredentials(username: string, domain: string): Promise<{username: string; password: string; domain: string; success: boolean}> {
const dialogRef = this.dialog.open(CredentialsModalComponent, {
data: {
username,

View File

@ -1,5 +1,7 @@
import { Observable } from 'rxjs';
import { UDSApiServiceType } from '../uds-api.service-type';
import { toPromise } from '../gui/uds-gui.service';
import { JSONTransportURLService } from '../types/services';
declare const django: any;
@ -15,255 +17,15 @@ export class Plugin {
this.delay = api.config.launcher_wait_time;
}
launchURL(url: string): void {
let state = 'init';
// Internal helper for notify errors
const notifyError = (error?: any) => {
let msg: string = django.gettext(
'Error communicating with your service. Please, retry again.'
);
if (typeof error === 'string') {
msg = error;
} else if (error.status === 403) {
// Session timeout
msg = django.gettext('Your session has expired. Please, login again');
}
window.setTimeout(() => {
this.showAlert(django.gettext('Error'), msg, 5000);
if (error.status === 403) {
window.setTimeout(() => {
this.api.logout();
}, 5000);
}
});
};
async launchURL(url: string): Promise<void> {
// If uds url...
if (url.substring(0, 7) === 'udsa://') {
const params = url.split('//')[1].split('/');
const alert = this.showAlert(
django.gettext('Please wait until the service is launched.'),
django.gettext(
'Remember that you will need the UDS client on your platform to access the service.'
),
0,
// Now UDS tries to check status
new Promise<boolean>((resolve) => {
let readyTime = 0;
const checker = () => {
if (alert.componentInstance) {
// Not closed dialog...
this.api.status(params[0], params[1]).then(
(data) => {
if (data.status === 'ready') {
if (!readyTime) {
readyTime = Date.now(); // Milisecodns
alert.componentInstance.data.title =
django.gettext('Service ready');
alert.componentInstance.data.body = django.gettext(
'Launching UDS Client, almost done.'
);
await this.processUDSUrl(url);
} else {
// If Component took too long...
if (Date.now() - readyTime > this.delay * 5) {
// Wait 5 times the default delay
alert.componentInstance.data.title =
django.gettext('Service ready') +
' - ' +
django.gettext('UDS Client not launching');
alert.componentInstance.data.body =
'<span style="color: red; ">' +
django.gettext(
'It seems that you don\'t have UDS Client installed. Please, install it from here:'
) +
'&nbsp;</span>' +
'<a href="' +
this.api.config.urls.clientDownload +
'">' +
django.gettext('UDS Client Download') +
'</a>';
await this.processExternalUrl(url);
}
}
window.setTimeout(checker, this.delay); // Recheck after delay seconds
} else if (data.status === 'accessed') {
alert.componentInstance.data.body = django.gettext(
'Machine ready, waiting for UDS Client'
);
resolve(true);
} else if (data.status === 'running') {
window.setTimeout(checker, this.delay); // Recheck after delay seconds
} else {
resolve(true);
notifyError();
}
},
(error) => {
resolve(true);
notifyError(error);
}
);
}
};
const init = () => {
if (state === 'init') {
window.setTimeout(init, this.delay);
} else if (state === 'error' || state === 'stop') {
return;
} else {
window.setTimeout(checker);
}
};
window.setTimeout(init);
})
);
this.api.enabler(params[0], params[1]).then(
(data) => {
if (data.error) {
state = 'error';
// TODO: show the error correctly
this.api.gui.alert(
django.gettext('Error launching service'),
data.error
);
} else {
// Is HTTP access the service returned, or for UDS client?
if (data.url.startsWith('/')) {
// If running message window, close it first...
if (alert.componentInstance) {
alert.componentInstance.close();
}
state = 'stop';
this.launchURL(data.url);
return;
}
if (window.location.protocol === 'https:') {
// Ensures that protocol is https also for plugin, fixing if needed UDS provided info
data.url = data.url.replace('uds://', 'udss://');
}
state = 'enabled';
this.doLaunch(data.url);
}
},
(error) => {
// Any error on requests will redirect to login
this.api.logout();
}
);
} else {
// Custom url, http/https
const alert = this.showAlert(
django.gettext('Please wait until the service is launched.'),
django.gettext(
'Your connection is being prepared. It will open on a new window when ready.'
),
0,
// Now UDS tries to check status before closing dialog...
new Promise<boolean>((resolve) => {
const checker = () => {
if (alert.componentInstance) {
// Not closed dialog...
this.api.transportUrl(url).then(
(data) => {
if (data.url) {
resolve(true);
// Extract if credentials modal is requested
let username = '';
let domain = '';
let askCredentials = false;
let ticket = '';
let scrambler = '';
if (data.url.indexOf('&creds=') !== -1) {
askCredentials = true;
// Extract username and domain "&creds=username@domain"
const creds = data.url.split('&creds=')[1];
if (creds.indexOf('@') !== -1) {
username = creds.split('@')[0];
domain = creds.split('@')[1];
} else {
username = creds;
}
// Remove credentials from url
data.url = data.url.split('&creds=')[0];
// From "data=..." extract ticket and scrambler that is ticket.scrambler
const values = data.url.split('data=')[1].split('&')[0].split('.');
ticket = values[0];
scrambler = values[1];
}
let wnd = 'global';
let location = data.url;
// check if on same window or not
if (data.url.indexOf('o_s_w=') !== -1) {
const osw = /(.*)&o_s_w=.*/.exec(data.url);
wnd = 'same';
location = osw[1];
//window.location.href = osw[1];
} else {
// If the url contains "o_n_w", will open the url on a new window ALWAYS
if (data.url.indexOf('o_n_w=') !== -1) {
// Extract window name from o_n_w parameter if present
const onw = /(.*)&o_n_w=([a-zA-Z0-9._-]*)/.exec(
data.url
);
if (onw) {
wnd = onw[2];
location = onw[1];
}
}
}
const openWindow = () => {
if (wnd === 'same') {
window.location.href = location;
} else {
if (Plugin.transportsWindow[wnd]) {
Plugin.transportsWindow[wnd].close();
}
Plugin.transportsWindow[wnd] = window.open(
data.url,
'uds_trans_' + wnd
);
}
};
// If credentials required, ask for them
if (askCredentials) {
this.api.gui
.askCredentials(username, domain)
.then((result) => {
// Update transport credentials
this.api.updateTransportTicket(ticket, scrambler,result.username, result.password, result.domain).then(
() => {
openWindow();
}
);
});
} else {
openWindow(); // Open window
}
} else if (!data.running) {
resolve(true);
notifyError(data.error);
} else {
window.setTimeout(checker, this.delay); // Recheck after 5 seconds
}
},
(error) => {
resolve(true);
notifyError(error);
}
);
}
};
window.setTimeout(checker);
})
);
}
}
private showAlert(
private async showAlert(
text: string,
info: string,
waitTime: number,
@ -286,7 +48,7 @@ export class Plugin {
*
* @param url uds url to be lauhcned
*/
private doLaunch(url: string) {
private launchUDSUrl(url: string) {
let elem: HTMLIFrameElement = document.getElementById(
'hiddenUdsLauncherIFrame'
) as HTMLIFrameElement;
@ -302,4 +64,233 @@ export class Plugin {
}
elem.contentWindow.location.href = url;
}
/**
* Process an UDS url
*
* @param url uds url (udsa://serviceId/transportId)
* @returns nothing
*/
private async processUDSUrl(url: string): Promise<void> {
// Extract params from url, serviceId and transportId
const params = url.split('//')[1].split('/');
if (params.length !== 2) {
await this.notifyError(django.gettext('Invalid UDS URL'));
return;
}
const serviceId = params[0];
const transportId = params[1];
const dialog = await this.showAlert(
django.gettext('Please wait until the service is launched.'),
django.gettext(
'Remember that you will need the UDS client on your platform to access the service.'
),
0
);
let cancel = false;
// Connect close dialog to "cancel" variable
toPromise(dialog.afterClosed()).then(() => (cancel = true));
let readyTime = -1;
try {
// Enable service
const enabledData = await this.api.enabler(serviceId, transportId);
if (enabledData.error) {
throw enabledData.error;
}
// Is HTTP access the service returned, or for UDS client?
if (enabledData.url.startsWith('/')) {
dialog.close();
await this.launchURL(enabledData.url);
return;
}
if (window.location.protocol === 'https:') {
// Ensures that protocol is https also for plugin, fixing if needed UDS provided info
enabledData.url = enabledData.url.replace('uds://', 'udss://');
}
// Launches UDS Client, using an iframe
await this.launchUDSUrl(enabledData.url);
while (!cancel) {
const data = await this.api.status(serviceId, transportId);
// Wait 5 times the default delay before notifying that client is not installed
if (readyTime > 0 && Date.now() - readyTime > this.delay * 5) {
dialog.componentInstance.data.title =
django.gettext('Service ready') +
' - ' +
django.gettext('UDS Client not launching');
dialog.componentInstance.data.body =
'<span style="color: red; ">' +
django.gettext(
'It seems that you don\'t have UDS Client installed. Please, install it from here:'
) +
'&nbsp;</span>' +
'<a href="' +
this.api.config.urls.clientDownload +
'">' +
django.gettext('UDS Client Download') +
'</a>';
}
if (data.status === 'ready') {
if (readyTime === -1) {
// Service is ready, wait for client, update dialog text
readyTime = Date.now(); // Milisecodns
dialog.componentInstance.data.title =
django.gettext('Service ready');
dialog.componentInstance.data.body = django.gettext(
'Launching UDS Client, almost done.'
);
}
} else if (data.status === 'accessed') {
// stop checking
dialog.close();
cancel = true;
continue;
} else if (data.status !== 'running') {
// Service is not running, close dialog and notify error
dialog.close();
await this.notifyError(data.status);
cancel = true;
continue;
}
// Wait a second before checking again
await this.api.sleep(1000);
}
} catch (error) {
dialog.close();
await this.notifyError(error);
}
}
private async processExternalUrl(url: string): Promise<void> {
const dialog = await this.showAlert(
django.gettext('Please wait until the service is launched.'),
django.gettext(
'Remember that you will need the UDS client on your platform to access the service.'
),
0
);
let cancel = false;
// Connect close dialog to "cancel" variable
toPromise(dialog.afterClosed()).then(() => (cancel = true));
try {
while (!cancel) {
const data = await this.api.transportUrl(url);
if (data.url) {
dialog.close();
// if ask credentials, show dialog
// Extract username and domain "&creds=username@domain"
const creds = await this.processCredentials(data);
if (creds !== null) {
await this.api.updateTransportTicket(
creds.ticket,
creds.scrambler,
creds.username,
creds.password,
creds.domain
);
}
this.openWindow(data.url);
cancel = true;
} else {
if (!data.running) {
dialog.close();
await this.notifyError();
cancel = true;
}
}
}
// Wait a second before checking again
await this.api.sleep(1000);
} catch (error) {
dialog.close();
await this.notifyError(error);
}
}
private async processCredentials(
data: JSONTransportURLService
): Promise<any> {
if (data.url.indexOf('&creds=') !== -1) {
const creds = data.url.split('&creds=')[1];
let username = '';
let domain = '';
// Remove credentials from url
data.url = data.url.split('&creds=')[0];
// From "data=..." extract ticket and scrambler that is ticket.scrambler
const values = data.url.split('data=')[1].split('&')[0].split('.');
const ticket = values[0];
const scrambler = values[1];
if (creds.indexOf('@') !== -1) {
username = creds.split('@')[0];
domain = creds.split('@')[1];
} else {
username = creds;
}
const result = await this.api.gui.askCredentials(username, domain);
if (result.success === false) {
throw new Error('User canceled credentials dialog');
}
return {
ticket,
scrambler,
username: result.username,
password: result.password,
domain: result.domain,
};
}
return null;
}
private openWindow(location: string): void {
let wnd = 'global';
// check if on same window or not
if (location.indexOf('o_s_w=') !== -1) {
const osw = /(.*)&o_s_w=.*/.exec(location);
wnd = 'same';
location = osw[1];
} else {
// If the url contains "o_n_w", will open the url on a new window ALWAYS
if (location.indexOf('o_n_w=') !== -1) {
// Extract window name from o_n_w parameter if present
const onw = /(.*)&o_n_w=([a-zA-Z0-9._-]*)/.exec(location);
if (onw) {
wnd = onw[2];
location = onw[1];
}
}
}
if (wnd === 'same') {
window.location.href = location;
} else {
if (Plugin.transportsWindow[wnd]) {
Plugin.transportsWindow[wnd].close();
}
Plugin.transportsWindow[wnd] = window.open(location, 'uds_trans_' + wnd);
}
}
private async notifyError(error?: any): Promise<void> {
let msg: string = django.gettext(
'Error communicating with your service. Please, retry again.'
);
if (typeof error === 'string') {
msg = error;
} else if (error instanceof Error) {
msg = error.message;
} else if (error.status === 403) {
// Session timeout
msg = django.gettext('Your session has expired. Please, login again');
}
await this.showAlert(django.gettext('Error'), msg, 5000);
if (error.status === 403) {
this.api.logout();
}
}
}

View File

@ -27,7 +27,7 @@ export class ErrorComponent implements OnInit {
}
this.error = '';
// Request error string from UDS
this.api.getErrorInformation(id).subscribe((errInfo) => {
this.api.getErrorInformation(id).then((errInfo) => {
// Set error to errInfo.error + Hex code
this.error = errInfo.error;
});

View File

@ -63,7 +63,7 @@ export class LoginComponent implements OnInit {
.setAttribute('style', 'display: none;');
this.api
.getAuthCustomHtml(l.id)
.subscribe((result) => doCustomAuth(result));
.then((result) => doCustomAuth(result));
}
}
}

View File

@ -42,6 +42,8 @@ export interface UDSApiServiceType {
/* Executes logout */
logout(): void;
/* sleep milliseconds */
sleep(ms: number): Promise<void>;
/**
* Gets services information
*/

View File

@ -94,7 +94,10 @@ export class UDSApiService implements UDSApiServiceType {
}
/* Client enabler */
enabler(serviceId: string, transportId: string): Promise<JSONEnabledService> {
async enabler(
serviceId: string,
transportId: string
): Promise<JSONEnabledService> {
const enabler = this.config.urls.enabler
.replace('param1', serviceId)
.replace('param2', transportId);
@ -102,7 +105,10 @@ export class UDSApiService implements UDSApiServiceType {
}
/* Check userService status */
status(serviceId: string, transportId: string): Promise<JSONStatusService> {
async status(
serviceId: string,
transportId: string
): Promise<JSONStatusService> {
const status = this.config.urls.status
.replace('param1', serviceId)
.replace('param2', transportId);
@ -110,18 +116,18 @@ export class UDSApiService implements UDSApiServiceType {
}
/* Services resetter */
action(action: string, serviceId: string): Promise<JSONService> {
async action(action: string, serviceId: string): Promise<JSONService> {
const actionURL = this.config.urls.action
.replace('param1', serviceId)
.replace('param2', action);
return toPromise(this.http.get<JSONService>(actionURL));
}
transportUrl(url: string): Promise<JSONTransportURLService> {
async transportUrl(url: string): Promise<JSONTransportURLService> {
return toPromise(this.http.get<JSONTransportURLService>(url));
}
updateTransportTicket(
async updateTransportTicket(
ticketId: string,
scrambler: string,
username: string,
@ -160,16 +166,20 @@ export class UDSApiService implements UDSApiServiceType {
/**
* Gets services information
*/
getServicesInformation(): Promise<JSONServicesInformation> {
return toPromise(this.http.get<JSONServicesInformation>(this.config.urls.services));
async getServicesInformation(): Promise<JSONServicesInformation> {
return toPromise(
this.http.get<JSONServicesInformation>(this.config.urls.services)
);
}
/**
* Gets error string from a code
*/
getErrorInformation(errorCode: string): Observable<JSONErrorInformation> {
return this.http.get<JSONErrorInformation>(
async getErrorInformation(errorCode: string): Promise<JSONErrorInformation> {
return toPromise(
this.http.get<JSONErrorInformation>(
this.config.urls.error.replace('9999', errorCode)
)
);
}
@ -193,6 +203,10 @@ export class UDSApiService implements UDSApiServiceType {
window.location.href = this.config.urls.logout;
}
sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
launchURL(udsURL: string): void {
this.plugin.launchURL(udsURL);
}
@ -203,10 +217,10 @@ export class UDSApiService implements UDSApiServiceType {
* @param authId if of the authenticator
* @returns Observable
*/
getAuthCustomHtml(authId: string) {
return this.http.get(this.config.urls.customAuth + authId, {
responseType: 'text',
});
async getAuthCustomHtml(authId: string): Promise<string> {
return toPromise(
this.http.get<string>(this.config.urls.customAuth + authId)
);
}
// Switch dark/light theme