Refactor devolutions-patch.js for reduced indent size
Refactor the `windows-rdp/devolutions-patch.js` script to use a two-space indentation style from a previous four-space style. This change improves code readability and consistency with the project's codebase formatting style. All functional code remains unaffected; only formatting has been adjusted.
This commit is contained in:
@@ -53,23 +53,23 @@ const SCREEN_POLL_INTERVAL_MS = 500;
|
|||||||
* @satisfies {FormFieldEntries}
|
* @satisfies {FormFieldEntries}
|
||||||
*/
|
*/
|
||||||
const formFieldEntries = {
|
const formFieldEntries = {
|
||||||
/** @readonly */
|
/** @readonly */
|
||||||
username: {
|
username: {
|
||||||
/** @readonly */
|
/** @readonly */
|
||||||
querySelector: "web-client-username-control input",
|
querySelector: "web-client-username-control input",
|
||||||
|
|
||||||
/** @readonly */
|
/** @readonly */
|
||||||
value: "${CODER_USERNAME}",
|
value: "${CODER_USERNAME}",
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @readonly */
|
/** @readonly */
|
||||||
password: {
|
password: {
|
||||||
/** @readonly */
|
/** @readonly */
|
||||||
querySelector: "web-client-password-control input",
|
querySelector: "web-client-password-control input",
|
||||||
|
|
||||||
/** @readonly */
|
/** @readonly */
|
||||||
value: "${CODER_PASSWORD}",
|
value: "${CODER_PASSWORD}",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,32 +93,32 @@ const formFieldEntries = {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
function setInputValue(inputField, inputText) {
|
function setInputValue(inputField, inputText) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Adding timeout for input event, even though we'll be dispatching it
|
// Adding timeout for input event, even though we'll be dispatching it
|
||||||
// immediately, just in the off chance that something in the Angular app
|
// immediately, just in the off chance that something in the Angular app
|
||||||
// intercepts it or stops it from propagating properly
|
// intercepts it or stops it from propagating properly
|
||||||
const timeoutId = window.setTimeout(() => {
|
const timeoutId = window.setTimeout(() => {
|
||||||
reject(new Error("Input event did not get processed correctly in time."));
|
reject(new Error("Input event did not get processed correctly in time."));
|
||||||
}, 3_000);
|
}, 3_000);
|
||||||
|
|
||||||
const handleSuccessfulDispatch = () => {
|
const handleSuccessfulDispatch = () => {
|
||||||
window.clearTimeout(timeoutId);
|
window.clearTimeout(timeoutId);
|
||||||
inputField.removeEventListener("input", handleSuccessfulDispatch);
|
inputField.removeEventListener("input", handleSuccessfulDispatch);
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
inputField.addEventListener("input", handleSuccessfulDispatch);
|
inputField.addEventListener("input", handleSuccessfulDispatch);
|
||||||
|
|
||||||
// Code assumes that Angular will have an event handler in place to handle
|
// Code assumes that Angular will have an event handler in place to handle
|
||||||
// the new event
|
// the new event
|
||||||
const inputEvent = new Event("input", {
|
const inputEvent = new Event("input", {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
inputField.value = inputText;
|
inputField.value = inputText;
|
||||||
inputField.dispatchEvent(inputEvent);
|
inputField.dispatchEvent(inputEvent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,91 +137,91 @@ function setInputValue(inputField, inputText) {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function autoSubmitForm(myForm) {
|
async function autoSubmitForm(myForm) {
|
||||||
const setProtocolValue = () => {
|
const setProtocolValue = () => {
|
||||||
/** @type {HTMLDivElement | null} */
|
/** @type {HTMLDivElement | null} */
|
||||||
const protocolDropdownTrigger = myForm.querySelector('div[role="button"]');
|
const protocolDropdownTrigger = myForm.querySelector('div[role="button"]');
|
||||||
if (protocolDropdownTrigger === null) {
|
if (protocolDropdownTrigger === null) {
|
||||||
throw new Error("No clickable trigger for setting protocol value");
|
throw new Error("No clickable trigger for setting protocol value");
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolDropdownTrigger.click();
|
protocolDropdownTrigger.click();
|
||||||
|
|
||||||
// Can't use form as container for querying the list of dropdown options,
|
// Can't use form as container for querying the list of dropdown options,
|
||||||
// because the elements don't actually exist inside the form. They're placed
|
// because the elements don't actually exist inside the form. They're placed
|
||||||
// in the top level of the HTML doc, and repositioned to make it look like
|
// in the top level of the HTML doc, and repositioned to make it look like
|
||||||
// they're part of the form. Avoids CSS stacking context issues, maybe?
|
// they're part of the form. Avoids CSS stacking context issues, maybe?
|
||||||
/** @type {HTMLLIElement | null} */
|
/** @type {HTMLLIElement | null} */
|
||||||
const protocolOption = document.querySelector(
|
const protocolOption = document.querySelector(
|
||||||
`p-dropdownitem[ng-reflect-label="${PROTOCOL}"] li`,
|
'p-dropdownitem[ng-reflect-label="' + PROTOCOL + '"] li',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (protocolOption === null) {
|
if (protocolOption === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Unable to find protocol option on screen that matches desired protocol",
|
"Unable to find protocol option on screen that matches desired protocol",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolOption.click();
|
protocolOption.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setHostname = () => {
|
const setHostname = () => {
|
||||||
/** @type {HTMLInputElement | null} */
|
/** @type {HTMLInputElement | null} */
|
||||||
const hostnameInput = myForm.querySelector("p-autocomplete#hostname input");
|
const hostnameInput = myForm.querySelector("p-autocomplete#hostname input");
|
||||||
|
|
||||||
if (hostnameInput === null) {
|
if (hostnameInput === null) {
|
||||||
throw new Error("Unable to find field for adding hostname");
|
throw new Error("Unable to find field for adding hostname");
|
||||||
}
|
}
|
||||||
|
|
||||||
return setInputValue(hostnameInput, HOSTNAME);
|
return setInputValue(hostnameInput, HOSTNAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCoderFormFieldValues = async () => {
|
const setCoderFormFieldValues = async () => {
|
||||||
// The RDP form will not appear on screen unless the dropdown is set to use
|
// The RDP form will not appear on screen unless the dropdown is set to use
|
||||||
// the RDP protocol
|
// the RDP protocol
|
||||||
const rdpSubsection = myForm.querySelector("rdp-form");
|
const rdpSubsection = myForm.querySelector("rdp-form");
|
||||||
if (rdpSubsection === null) {
|
if (rdpSubsection === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Unable to find RDP subsection. Is the value of the protocol set to RDP?",
|
"Unable to find RDP subsection. Is the value of the protocol set to RDP?",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const { value, querySelector } of Object.values(formFieldEntries)) {
|
for (const { value, querySelector } of Object.values(formFieldEntries)) {
|
||||||
/** @type {HTMLInputElement | null} */
|
/** @type {HTMLInputElement | null} */
|
||||||
const input = document.querySelector(querySelector);
|
const input = document.querySelector(querySelector);
|
||||||
|
|
||||||
if (input === null) {
|
if (input === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to element that matches query "${querySelector}"`,
|
'Unable to element that matches query "' + querySelector + '"',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await setInputValue(input, value);
|
await setInputValue(input, value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const triggerSubmission = () => {
|
const triggerSubmission = () => {
|
||||||
/** @type {HTMLButtonElement | null} */
|
/** @type {HTMLButtonElement | null} */
|
||||||
const submitButton = myForm.querySelector(
|
const submitButton = myForm.querySelector(
|
||||||
'p-button[ng-reflect-type="submit"] button',
|
'p-button[ng-reflect-type="submit"] button',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (submitButton === null) {
|
if (submitButton === null) {
|
||||||
throw new Error("Unable to find submission button");
|
throw new Error("Unable to find submission button");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (submitButton.disabled) {
|
if (submitButton.disabled) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Unable to submit form because submit button is disabled. Are all fields filled out correctly?",
|
"Unable to submit form because submit button is disabled. Are all fields filled out correctly?",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitButton.click();
|
submitButton.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
setProtocolValue();
|
setProtocolValue();
|
||||||
await setHostname();
|
await setHostname();
|
||||||
await setCoderFormFieldValues();
|
await setCoderFormFieldValues();
|
||||||
triggerSubmission();
|
triggerSubmission();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,58 +231,58 @@ async function autoSubmitForm(myForm) {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function setupFormDetection() {
|
function setupFormDetection() {
|
||||||
/** @type {HTMLFormElement | null} */
|
/** @type {HTMLFormElement | null} */
|
||||||
let formValueFromLastMutation = null;
|
let formValueFromLastMutation = null;
|
||||||
|
|
||||||
/** @returns {void} */
|
/** @returns {void} */
|
||||||
const onDynamicTabMutation = () => {
|
const onDynamicTabMutation = () => {
|
||||||
/** @type {HTMLFormElement | null} */
|
/** @type {HTMLFormElement | null} */
|
||||||
const latestForm = document.querySelector("web-client-form > form");
|
const latestForm = document.querySelector("web-client-form > form");
|
||||||
|
|
||||||
// Only try to auto-fill if we went from having no form on screen to
|
// Only try to auto-fill if we went from having no form on screen to
|
||||||
// having a form on screen. That way, we don't accidentally override the
|
// having a form on screen. That way, we don't accidentally override the
|
||||||
// form if the user is trying to customize values, and this essentially
|
// form if the user is trying to customize values, and this essentially
|
||||||
// makes the script values function as default values
|
// makes the script values function as default values
|
||||||
const mounted = formValueFromLastMutation === null && latestForm !== null;
|
const mounted = formValueFromLastMutation === null && latestForm !== null;
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
autoSubmitForm(latestForm);
|
autoSubmitForm(latestForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
formValueFromLastMutation = latestForm;
|
formValueFromLastMutation = latestForm;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {number | undefined} */
|
/** @type {number | undefined} */
|
||||||
let pollingId = undefined;
|
let pollingId = undefined;
|
||||||
|
|
||||||
/** @returns {void} */
|
/** @returns {void} */
|
||||||
const checkScreenForDynamicTab = () => {
|
const checkScreenForDynamicTab = () => {
|
||||||
const dynamicTab = document.querySelector("web-client-dynamic-tab");
|
const dynamicTab = document.querySelector("web-client-dynamic-tab");
|
||||||
|
|
||||||
// Keep polling until the main content container is on screen
|
// Keep polling until the main content container is on screen
|
||||||
if (dynamicTab === null) {
|
if (dynamicTab === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.clearInterval(pollingId);
|
window.clearInterval(pollingId);
|
||||||
|
|
||||||
// Call the mutation callback manually, to ensure it runs at least once
|
// Call the mutation callback manually, to ensure it runs at least once
|
||||||
onDynamicTabMutation();
|
onDynamicTabMutation();
|
||||||
|
|
||||||
// Having the mutation observer is kind of an extra safety net that isn't
|
// Having the mutation observer is kind of an extra safety net that isn't
|
||||||
// really expected to run that often. Most of the content in the dynamic
|
// really expected to run that often. Most of the content in the dynamic
|
||||||
// tab is being rendered through Canvas, which won't trigger any mutations
|
// tab is being rendered through Canvas, which won't trigger any mutations
|
||||||
// that the observer can detect
|
// that the observer can detect
|
||||||
const dynamicTabObserver = new MutationObserver(onDynamicTabMutation);
|
const dynamicTabObserver = new MutationObserver(onDynamicTabMutation);
|
||||||
dynamicTabObserver.observe(dynamicTab, {
|
dynamicTabObserver.observe(dynamicTab, {
|
||||||
subtree: true,
|
subtree: true,
|
||||||
childList: true,
|
childList: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
pollingId = window.setInterval(
|
pollingId = window.setInterval(
|
||||||
checkScreenForDynamicTab,
|
checkScreenForDynamicTab,
|
||||||
SCREEN_POLL_INTERVAL_MS,
|
SCREEN_POLL_INTERVAL_MS,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,34 +292,34 @@ function setupFormDetection() {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function setupAlwaysOnStyles() {
|
function setupAlwaysOnStyles() {
|
||||||
const styleId = "coder-patch--styles-always-on";
|
const styleId = "coder-patch--styles-always-on";
|
||||||
const existingContainer = document.querySelector(`#${styleId}`);
|
const existingContainer = document.querySelector("#" + styleId);
|
||||||
if (existingContainer) {
|
if (existingContainer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleContainer = document.createElement("style");
|
const styleContainer = document.createElement("style");
|
||||||
styleContainer.id = styleId;
|
styleContainer.id = styleId;
|
||||||
styleContainer.innerHTML = `
|
styleContainer.innerHTML = `
|
||||||
/* app-menu corresponds to the sidebar of the default view. */
|
/* app-menu corresponds to the sidebar of the default view. */
|
||||||
app-menu {
|
app-menu {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.head.appendChild(styleContainer);
|
document.head.appendChild(styleContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideFormForInitialSubmission() {
|
function hideFormForInitialSubmission() {
|
||||||
const styleId = "coder-patch--styles-initial-submission";
|
const styleId = "coder-patch--styles-initial-submission";
|
||||||
const cssOpacityVariableName = "--coder-opacity-multiplier";
|
const cssOpacityVariableName = "--coder-opacity-multiplier";
|
||||||
|
|
||||||
/** @type {HTMLStyleElement | null} */
|
/** @type {HTMLStyleElement | null} */
|
||||||
let styleContainer = document.querySelector(`#${styleId}`);
|
let styleContainer = document.querySelector("#" + styleId);
|
||||||
if (!styleContainer) {
|
if (!styleContainer) {
|
||||||
styleContainer = document.createElement("style");
|
styleContainer = document.createElement("style");
|
||||||
styleContainer.id = styleId;
|
styleContainer.id = styleId;
|
||||||
styleContainer.innerHTML = `
|
styleContainer.innerHTML = `
|
||||||
/*
|
/*
|
||||||
Have to use opacity instead of visibility, because the element still
|
Have to use opacity instead of visibility, because the element still
|
||||||
needs to be interactive via the script so that it can be auto-filled.
|
needs to be interactive via the script so that it can be auto-filled.
|
||||||
@@ -350,50 +350,50 @@ function hideFormForInitialSubmission() {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.head.appendChild(styleContainer);
|
document.head.appendChild(styleContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The root node being undefined should be physically impossible (if it's
|
// The root node being undefined should be physically impossible (if it's
|
||||||
// undefined, the browser itself is busted), but we need to do a type check
|
// undefined, the browser itself is busted), but we need to do a type check
|
||||||
// here so that the rest of the function doesn't need to do type checks over
|
// here so that the rest of the function doesn't need to do type checks over
|
||||||
// and over.
|
// and over.
|
||||||
const rootNode = document.querySelector(":root");
|
const rootNode = document.querySelector(":root");
|
||||||
if (!(rootNode instanceof HTMLHtmlElement)) {
|
if (!(rootNode instanceof HTMLHtmlElement)) {
|
||||||
// Remove the container entirely because if the browser is busted, who knows
|
// Remove the container entirely because if the browser is busted, who knows
|
||||||
// if the CSS variables can be applied correctly. Better to have something
|
// if the CSS variables can be applied correctly. Better to have something
|
||||||
// be a bit more ugly/painful to use, than have it be impossible to use
|
// be a bit more ugly/painful to use, than have it be impossible to use
|
||||||
styleContainer.remove();
|
styleContainer.remove();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's safe to make the form visible preemptively because Devolutions
|
// It's safe to make the form visible preemptively because Devolutions
|
||||||
// outputs the Windows view through an HTML canvas that it overlays on top
|
// outputs the Windows view through an HTML canvas that it overlays on top
|
||||||
// of the rest of the app. Even if the form isn't hidden at the style level,
|
// of the rest of the app. Even if the form isn't hidden at the style level,
|
||||||
// it will still be covered up.
|
// it will still be covered up.
|
||||||
const restoreOpacity = () => {
|
const restoreOpacity = () => {
|
||||||
rootNode.style.setProperty(cssOpacityVariableName, "1");
|
rootNode.style.setProperty(cssOpacityVariableName, "1");
|
||||||
};
|
};
|
||||||
|
|
||||||
// If this file gets more complicated, it might make sense to set up the
|
// If this file gets more complicated, it might make sense to set up the
|
||||||
// timeout and event listener so that if one triggers, it cancels the other,
|
// timeout and event listener so that if one triggers, it cancels the other,
|
||||||
// but having restoreOpacity run more than once is a no-op for right now.
|
// but having restoreOpacity run more than once is a no-op for right now.
|
||||||
// Not a big deal if these don't get cleaned up.
|
// Not a big deal if these don't get cleaned up.
|
||||||
|
|
||||||
// Have the form automatically reappear no matter what, so that if something
|
// Have the form automatically reappear no matter what, so that if something
|
||||||
// does break, the user isn't left out to dry
|
// does break, the user isn't left out to dry
|
||||||
window.setTimeout(restoreOpacity, 5_000);
|
window.setTimeout(restoreOpacity, 5_000);
|
||||||
|
|
||||||
/** @type {HTMLFormElement | null} */
|
/** @type {HTMLFormElement | null} */
|
||||||
const form = document.querySelector("web-client-form > form");
|
const form = document.querySelector("web-client-form > form");
|
||||||
form?.addEventListener(
|
form?.addEventListener(
|
||||||
"submit",
|
"submit",
|
||||||
() => {
|
() => {
|
||||||
// Not restoring opacity right away just to give the HTML canvas a little
|
// Not restoring opacity right away just to give the HTML canvas a little
|
||||||
// bit of time to get spun up and cover up the main form
|
// bit of time to get spun up and cover up the main form
|
||||||
window.setTimeout(restoreOpacity, 1_000);
|
window.setTimeout(restoreOpacity, 1_000);
|
||||||
},
|
},
|
||||||
{ once: true },
|
{ once: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always safe to call these immediately because even if the Angular app isn't
|
// Always safe to call these immediately because even if the Angular app isn't
|
||||||
@@ -403,7 +403,7 @@ setupAlwaysOnStyles();
|
|||||||
hideFormForInitialSubmission();
|
hideFormForInitialSubmission();
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener("DOMContentLoaded", setupFormDetection);
|
document.addEventListener("DOMContentLoaded", setupFormDetection);
|
||||||
} else {
|
} else {
|
||||||
setupFormDetection();
|
setupFormDetection();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user