Восстанови старую схему автозаполнения VPN
This commit is contained in:
10
README.md
10
README.md
@@ -369,10 +369,9 @@ curl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh \
|
||||
| Патч | Что меняет | Зачем |
|
||||
| --- | --- | --- |
|
||||
| `minimal -> offscreen` | Меняет Qt platform mode для скрытого браузера | `minimal` падает с Qt WebEngine на macOS |
|
||||
| `input/change events` | После `value = ...` отправляет DOM events | Keycloak не реагирует на прямую запись value |
|
||||
| `native value setter` | Заполняет поля через нативный setter `HTMLInputElement` | React/Keycloak корректнее видит изменение значения |
|
||||
| `input/change events` | Оставляет старое прямое `value = ...`, но после него отправляет DOM events | Keycloak не реагирует на прямую запись value без событий |
|
||||
| `legacy auto-fill` | Сохраняет старую рабочую схему `ApplicationWorld`, прямой `value = ...` и простой `click()` | Это ровно тот режим, на котором hidden SSO раньше стабильно проходил Keycloak |
|
||||
| `URL guard` | Проверяет `location.href` через `new RegExp(...)` перед auto-fill | Qt игнорирует `@include`, без guard auto-fill может кликнуть Cisco ACS и сломать SAML |
|
||||
| `submit click guard` | Кликает submit один раз на страницу и только после заполнения поля | Без guard hidden-браузер может зациклиться на Keycloak `login-actions/authenticate` |
|
||||
| `manual SSO disable` | Позволяет отключить auto-fill через `LEMANA_VPN_AUTOFILL_DISABLE=1` | Нужен для ручной диагностики в видимом браузере |
|
||||
|
||||
Перед первым изменением CLI сохраняет оригинальный файл:
|
||||
@@ -435,10 +434,9 @@ vpn --manual-sso --debug
|
||||
CLI перед подключением патчит `openconnect-lite`:
|
||||
|
||||
- `minimal` -> `offscreen`, чтобы Qt WebEngine не падал на macOS;
|
||||
- добавляет `input` и `change` events для Keycloak auto-fill;
|
||||
- заполняет поля через native value setter;
|
||||
- добавляет `input` и `change` events для Keycloak auto-fill, сохраняя старое прямое присваивание `value = ...`;
|
||||
- оставляет auto-fill в старом `ApplicationWorld` и не добавляет stateful click guards/native setters;
|
||||
- добавляет URL guard, чтобы auto-fill не кликал submit на Cisco ACS;
|
||||
- добавляет submit click guard, чтобы auto-fill не отправлял одну и ту же Keycloak форму бесконечно.
|
||||
- добавляет manual SSO disable для видимой ручной диагностики без auto-fill.
|
||||
|
||||
## Удаление
|
||||
|
||||
@@ -160,7 +160,8 @@ _patches_active() {
|
||||
grep -q '"offscreen"' "$wep" \
|
||||
&& grep -q 'new Event("input", {{bubbles: true}})' "$wep" \
|
||||
&& grep -q 'new RegExp' "$wep" \
|
||||
&& grep -q '__lemanaVpnClicked' "$wep"
|
||||
&& grep -q 'LEMANA_VPN_AUTOFILL_DISABLE' "$wep" \
|
||||
&& ! grep -Eq '__lemanaVpnClicked|valueSetter|ScriptWorldId.MainWorld' "$wep"
|
||||
}
|
||||
|
||||
_keychain_has() {
|
||||
@@ -332,88 +333,70 @@ backup_dir = Path(sys.argv[2])
|
||||
backup_file = backup_dir / "webengine_process.py.before-lemana-vpn"
|
||||
src = path.read_text()
|
||||
before = src
|
||||
original = src
|
||||
messages = []
|
||||
|
||||
src = src.replace('argv += ["-platform", "minimal"]', 'argv += ["-platform", "offscreen"]')
|
||||
if src != original:
|
||||
messages.append("minimal -> offscreen")
|
||||
original = src
|
||||
|
||||
if "import os" not in src:
|
||||
src = src.replace("import json\n", "import json\nimport os\n")
|
||||
messages.append("autofill debug import")
|
||||
def fail(label):
|
||||
print(f"Cannot apply {label} patch: unsupported openconnect-lite source", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def replace_once(old, new, label, required=False):
|
||||
global src
|
||||
if old in src:
|
||||
src = src.replace(old, new, 1)
|
||||
messages.append(label)
|
||||
return True
|
||||
if required:
|
||||
fail(label)
|
||||
return False
|
||||
|
||||
|
||||
replace_once(
|
||||
'argv += ["-platform", "minimal"]',
|
||||
'argv += ["-platform", "offscreen"]',
|
||||
"minimal -> offscreen",
|
||||
)
|
||||
if '"offscreen"' not in src:
|
||||
fail("minimal -> offscreen")
|
||||
|
||||
if "\nimport os\n" not in src:
|
||||
replace_once("import json\n", "import json\nimport os\n", "manual SSO import", required=True)
|
||||
|
||||
if "Autologin disabled by Lemana VPN" not in src:
|
||||
old_autologin = ''' if credentials:
|
||||
replace_once(
|
||||
''' if credentials:
|
||||
logger.info("Initiating autologin", cred=credentials)
|
||||
'''
|
||||
new_autologin = ''' if os.environ.get("LEMANA_VPN_AUTOFILL_DISABLE") == "1":
|
||||
''',
|
||||
''' if os.environ.get("LEMANA_VPN_AUTOFILL_DISABLE") == "1":
|
||||
logger.info("Autologin disabled by Lemana VPN")
|
||||
elif credentials:
|
||||
logger.info("Initiating autologin", cred=credentials)
|
||||
'''
|
||||
if old_autologin not in src:
|
||||
print("Cannot apply manual SSO patch: unsupported openconnect-lite source", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
src = src.replace(old_autologin, new_autologin)
|
||||
messages.append("manual SSO disable")
|
||||
|
||||
if 'console.info("lemana autofill: " + message);' in src:
|
||||
src = src.replace(
|
||||
'console.info("lemana autofill: " + message);',
|
||||
'console.warn("lemana autofill: " + message);',
|
||||
''',
|
||||
"manual SSO disable",
|
||||
required=True,
|
||||
)
|
||||
messages.append("autofill diagnostics warning log")
|
||||
|
||||
plain_value_set = 'elem.value = {value}; window.__lemanaVpnFilled = true;'
|
||||
native_value_set = 'var valueSetter = Object.getOwnPropertyDescriptor(elem.__proto__, "value") || Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value"); if (valueSetter && valueSetter.set) {{ valueSetter.set.call(elem, {value}); }} else {{ elem.value = {value}; }} window.__lemanaVpnFilled = true;'
|
||||
if plain_value_set in src:
|
||||
src = src.replace(plain_value_set, native_value_set)
|
||||
messages.append("native value setter")
|
||||
replace_once(
|
||||
''' script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
|
||||
script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
|
||||
''',
|
||||
''' script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
|
||||
script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
|
||||
''',
|
||||
"restore auto-fill ApplicationWorld",
|
||||
)
|
||||
|
||||
plain_click = 'elem.dispatchEvent(new Event("focus")); elem.click();'
|
||||
delayed_click = 'elem.dispatchEvent(new Event("focus")); setTimeout(function() {{ if (document.contains(elem)) {{ elem.click(); }} }}, 250);'
|
||||
if plain_click in src:
|
||||
src = src.replace(plain_click, delayed_click)
|
||||
messages.append("delayed submit click")
|
||||
src = src.replace(
|
||||
' # Convert glob pattern to JS regex (not literal — URLs contain /)\n',
|
||||
'',
|
||||
)
|
||||
src = src.replace(
|
||||
' # Convert glob pattern to JS regex (not literal - URLs contain /)\n',
|
||||
'',
|
||||
)
|
||||
|
||||
old_fill = 'elem.dispatchEvent(new Event("focus")); elem.value = {value}; elem.dispatchEvent(new Event("blur"));'
|
||||
fill_with_events = 'elem.dispatchEvent(new Event("focus")); elem.value = {value}; elem.dispatchEvent(new Event("input", {{bubbles: true}})); elem.dispatchEvent(new Event("change", {{bubbles: true}})); elem.dispatchEvent(new Event("blur"));'
|
||||
new_fill = 'elem.dispatchEvent(new Event("focus")); var valueSetter = Object.getOwnPropertyDescriptor(elem.__proto__, "value") || Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value"); if (valueSetter && valueSetter.set) {{ valueSetter.set.call(elem, {value}); }} else {{ elem.value = {value}; }} window.__lemanaVpnFilled = true; elem.dispatchEvent(new Event("input", {{bubbles: true}})); elem.dispatchEvent(new Event("change", {{bubbles: true}})); elem.dispatchEvent(new Event("blur"));'
|
||||
if old_fill in src:
|
||||
src = src.replace(old_fill, new_fill)
|
||||
messages.append("input/change events")
|
||||
elif fill_with_events in src:
|
||||
src = src.replace(fill_with_events, new_fill)
|
||||
messages.append("fill marker")
|
||||
|
||||
old_click = 'var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.click(); }}'
|
||||
new_click = 'var elem = document.querySelector({selector}); if (elem && !elem.disabled && elem.offsetParent !== null && window.__lemanaVpnFilled) {{ window.__lemanaVpnClicked = window.__lemanaVpnClicked || {{}}; var clickKey = location.href + "|" + {selector}; if (!window.__lemanaVpnClicked[clickKey]) {{ window.__lemanaVpnClicked[clickKey] = true; elem.dispatchEvent(new Event("focus")); setTimeout(function() {{ if (document.contains(elem)) {{ elem.click(); }} }}, 250); }} }}'
|
||||
if old_click in src:
|
||||
src = src.replace(old_click, new_click)
|
||||
messages.append("submit click guard")
|
||||
|
||||
stop_plain = 'var elem = document.querySelector({selector}); if (elem) {{ return; }}'
|
||||
stop_debug = 'var elem = document.querySelector({selector}); if (elem && elem.offsetParent !== null && (elem.textContent || "").trim()) {{ _afLog("stop selector=" + {selector} + " text=" + (elem.textContent || "").trim().slice(0, 80)); return; }}'
|
||||
if stop_plain in src:
|
||||
src = src.replace(stop_plain, stop_debug)
|
||||
messages.append("visible stop guard")
|
||||
|
||||
fill_plain = 'var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.value = {value}; window.__lemanaVpnFilled = true; elem.dispatchEvent(new Event("input", {{bubbles: true}})); elem.dispatchEvent(new Event("change", {{bubbles: true}})); elem.dispatchEvent(new Event("blur")); }}'
|
||||
fill_debug = 'var elem = document.querySelector({selector}); if (elem) {{ _afLog("fill " + {rule.fill!r} + " selector=" + {selector} + " tag=" + elem.tagName + " type=" + (elem.type || "") + " id=" + (elem.id || "") + " name=" + (elem.name || "") + " value_len=" + String({value}.length)); elem.dispatchEvent(new Event("focus")); var valueSetter = Object.getOwnPropertyDescriptor(elem.__proto__, "value") || Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value"); if (valueSetter && valueSetter.set) {{ valueSetter.set.call(elem, {value}); }} else {{ elem.value = {value}; }} window.__lemanaVpnFilled = true; elem.dispatchEvent(new Event("input", {{bubbles: true}})); elem.dispatchEvent(new Event("change", {{bubbles: true}})); elem.dispatchEvent(new Event("blur")); }} else {{ _afLog("missing " + {rule.fill!r} + " selector=" + {selector}); }}'
|
||||
if fill_plain in src and "_afLog(\"fill \"" not in src:
|
||||
src = src.replace(fill_plain, fill_debug)
|
||||
messages.append("fill diagnostics")
|
||||
|
||||
click_plain = 'var elem = document.querySelector({selector}); if (elem && !elem.disabled && elem.offsetParent !== null && window.__lemanaVpnFilled) {{ window.__lemanaVpnClicked = window.__lemanaVpnClicked || {{}}; var clickKey = location.href + "|" + {selector}; if (!window.__lemanaVpnClicked[clickKey]) {{ window.__lemanaVpnClicked[clickKey] = true; elem.dispatchEvent(new Event("focus")); elem.click(); }} }}'
|
||||
click_debug = 'var elem = document.querySelector({selector}); window.__lemanaVpnClicked = window.__lemanaVpnClicked || {{}}; var clickKey = location.href + "|" + {selector}; _afLog("click-check selector=" + {selector} + " found=" + Boolean(elem) + " filled=" + Boolean(window.__lemanaVpnFilled) + " disabled=" + Boolean(elem && elem.disabled) + " visible=" + Boolean(elem && elem.offsetParent !== null) + " already=" + Boolean(window.__lemanaVpnClicked[clickKey])); if (elem && !elem.disabled && elem.offsetParent !== null && window.__lemanaVpnFilled && !window.__lemanaVpnClicked[clickKey]) {{ window.__lemanaVpnClicked[clickKey] = true; _afLog("click selector=" + {selector}); elem.dispatchEvent(new Event("focus")); setTimeout(function() {{ if (document.contains(elem)) {{ elem.click(); }} }}, 250); }}'
|
||||
if click_plain in src and 'click-check selector=' not in src:
|
||||
src = src.replace(click_plain, click_debug)
|
||||
messages.append("click diagnostics")
|
||||
|
||||
if "new RegExp" not in src:
|
||||
old_block = ''' script.setSourceCode(
|
||||
old_script = ''' script.setSourceCode(
|
||||
f"""
|
||||
// ==UserScript==
|
||||
// @include {url_pattern}
|
||||
@@ -426,9 +409,9 @@ function autoFill() {{
|
||||
autoFill();
|
||||
"""
|
||||
)'''
|
||||
new_block = ''' regex_str = "^" + url_pattern.replace(".", "\\\\.").replace("*", ".*") + "$"
|
||||
|
||||
canonical_script = ''' regex_str = "^" + url_pattern.replace(".", "\\\\.").replace("*", ".*") + "$"
|
||||
js_regex_str = json.dumps(regex_str)
|
||||
js_debug = "true" if os.environ.get("LEMANA_VPN_AUTOFILL_DEBUG") == "1" else "false"
|
||||
script.setSourceCode(
|
||||
f"""
|
||||
// ==UserScript==
|
||||
@@ -437,12 +420,6 @@ autoFill();
|
||||
|
||||
var _afUrlPattern = new RegExp({js_regex_str});
|
||||
var _afRun = 0;
|
||||
var _afDebug = {js_debug};
|
||||
function _afLog(message) {{
|
||||
if (_afDebug && _afRun <= 5) {{
|
||||
console.warn("lemana autofill: " + message);
|
||||
}}
|
||||
}}
|
||||
function autoFill() {{
|
||||
if (!_afUrlPattern.test(location.href.split('?')[0]) && !_afUrlPattern.test(location.href)) {{
|
||||
_afRun++;
|
||||
@@ -455,84 +432,58 @@ function autoFill() {{
|
||||
}}
|
||||
autoFill();
|
||||
"""
|
||||
)'''
|
||||
if old_block not in src:
|
||||
print("Cannot apply URL guard patch: unsupported openconnect-lite source", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
src = src.replace(old_block, new_block)
|
||||
)
|
||||
'''
|
||||
|
||||
script_marker = ''' regex_str = "^" + url_pattern.replace(".", "\\\\.").replace("*", ".*") + "$"
|
||||
'''
|
||||
script_end_marker = ''' self.page().scripts().insert(script)
|
||||
'''
|
||||
if script_marker in src:
|
||||
start = src.index(script_marker)
|
||||
end = src.index(script_end_marker, start)
|
||||
if src[start:end] != canonical_script:
|
||||
src = src[:start] + canonical_script + src[end:]
|
||||
messages.append("URL guard")
|
||||
elif old_script in src:
|
||||
src = src.replace(old_script, canonical_script.rstrip(), 1)
|
||||
messages.append("URL guard")
|
||||
else:
|
||||
fail("URL guard")
|
||||
|
||||
if "js_debug =" not in src:
|
||||
marker = ''' js_regex_str = json.dumps(regex_str)
|
||||
script.setSourceCode(
|
||||
selectors_marker = "def get_selectors(rules, credentials):\n"
|
||||
canonical_selectors = '''def get_selectors(rules, credentials):
|
||||
statements = []
|
||||
for rule in rules:
|
||||
selector = json.dumps(rule.selector)
|
||||
if rule.action == "stop":
|
||||
statements.append(
|
||||
f"""var elem = document.querySelector({selector}); if (elem) {{ return; }}"""
|
||||
)
|
||||
elif rule.fill:
|
||||
value = json.dumps(getattr(credentials, rule.fill, None))
|
||||
if value:
|
||||
statements.append(
|
||||
f"""var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.value = {value}; elem.dispatchEvent(new Event("input", {{bubbles: true}})); elem.dispatchEvent(new Event("change", {{bubbles: true}})); elem.dispatchEvent(new Event("blur")); }}"""
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Credential info not available",
|
||||
type=rule.fill,
|
||||
possibilities=dir(credentials),
|
||||
)
|
||||
elif rule.action == "click":
|
||||
statements.append(
|
||||
f"""var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.click(); }}"""
|
||||
)
|
||||
return "\\n".join(statements)
|
||||
'''
|
||||
if marker in src:
|
||||
src = src.replace(marker, ''' js_regex_str = json.dumps(regex_str)
|
||||
js_debug = "true" if os.environ.get("LEMANA_VPN_AUTOFILL_DEBUG") == "1" else "false"
|
||||
script.setSourceCode(
|
||||
''')
|
||||
messages.append("autofill debug flag")
|
||||
elif "_afLog(" in src:
|
||||
print("Cannot apply autofill debug flag patch: unsupported openconnect-lite source", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if "function _afLog" not in src:
|
||||
marker = '''var _afUrlPattern = new RegExp({js_regex_str});
|
||||
var _afRun = 0;
|
||||
function autoFill() {{
|
||||
'''
|
||||
if marker in src:
|
||||
src = src.replace(marker, '''var _afUrlPattern = new RegExp({js_regex_str});
|
||||
var _afRun = 0;
|
||||
var _afDebug = {js_debug};
|
||||
function _afLog(message) {{
|
||||
if (_afDebug && _afRun <= 5) {{
|
||||
console.warn("lemana autofill: " + message);
|
||||
}}
|
||||
}}
|
||||
function autoFill() {{
|
||||
''')
|
||||
messages.append("autofill diagnostics")
|
||||
elif "_afLog(" in src:
|
||||
print("Cannot apply autofill diagnostics patch: unsupported openconnect-lite source", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
page_state = ''' if (window.__lemanaVpnPageHref !== location.href) {{
|
||||
window.__lemanaVpnPageHref = location.href;
|
||||
window.__lemanaVpnFilled = false;
|
||||
window.__lemanaVpnClicked = {{}};
|
||||
}}
|
||||
if (_afDebug && _afRun === 1 && document.body) {{
|
||||
_afLog("body=" + (document.body.innerText || "").replace(/\\s+/g, " ").trim().slice(0, 180));
|
||||
}}
|
||||
'''
|
||||
if "window.__lemanaVpnPageHref" not in src:
|
||||
marker = ''' _afRun++;
|
||||
|
||||
{get_selectors(rules, credentials)}
|
||||
'''
|
||||
if marker not in src:
|
||||
print("Cannot apply page state guard patch: unsupported openconnect-lite source", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
src = src.replace(marker, ''' _afRun++;
|
||||
''' + page_state + '''
|
||||
{get_selectors(rules, credentials)}
|
||||
''')
|
||||
messages.append("page state guard")
|
||||
|
||||
if 'body=" + (document.body.innerText || "")' not in src:
|
||||
marker = ''' if (window.__lemanaVpnPageHref !== location.href) {{
|
||||
window.__lemanaVpnPageHref = location.href;
|
||||
window.__lemanaVpnFilled = false;
|
||||
window.__lemanaVpnClicked = {{}};
|
||||
}}
|
||||
'''
|
||||
if marker in src:
|
||||
src = src.replace(marker, marker + ''' if (_afDebug && _afRun === 1 && document.body) {{
|
||||
_afLog("body=" + (document.body.innerText || "").replace(/\\s+/g, " ").trim().slice(0, 180));
|
||||
}}
|
||||
''')
|
||||
messages.append("body diagnostics")
|
||||
if selectors_marker not in src:
|
||||
fail("auto-fill selectors")
|
||||
selectors_start = src.index(selectors_marker)
|
||||
if src[selectors_start:] != canonical_selectors:
|
||||
src = src[:selectors_start] + canonical_selectors
|
||||
messages.append("input/change events")
|
||||
|
||||
if src != before:
|
||||
backup_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -37,7 +37,88 @@ grep -q '"event":"waiting"' "$ROOT/bin/vpn-lemanapro.sh"
|
||||
grep -q -- '--patch-only' "$ROOT/bin/vpn-lemanapro.sh"
|
||||
grep -q -- '--manual-sso' "$ROOT/bin/vpn-lemanapro.sh"
|
||||
grep -q 'LEMANA_VPN_AUTOFILL_DISABLE' "$ROOT/bin/vpn-lemanapro.sh"
|
||||
grep -q '__lemanaVpnClicked' "$ROOT/bin/vpn-lemanapro.sh"
|
||||
|
||||
fake_webengine="$TMP_DIR/webengine_process.py"
|
||||
cat > "$fake_webengine" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
class Browser:
|
||||
def run(self, display_mode, credentials, url_pattern, rules):
|
||||
argv = sys.argv.copy()
|
||||
if display_mode == "hidden":
|
||||
argv += ["-platform", "minimal"]
|
||||
|
||||
if credentials:
|
||||
logger.info("Initiating autologin", cred=credentials)
|
||||
for url_pattern, rules in auto_fill_rules.items():
|
||||
script = QWebEngineScript()
|
||||
script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
|
||||
script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
|
||||
script.setSourceCode(
|
||||
f"""
|
||||
// ==UserScript==
|
||||
// @include {url_pattern}
|
||||
// ==/UserScript==
|
||||
|
||||
function autoFill() {{
|
||||
{get_selectors(rules, credentials)}
|
||||
setTimeout(autoFill, 1000);
|
||||
}}
|
||||
autoFill();
|
||||
"""
|
||||
)
|
||||
self.page().scripts().insert(script)
|
||||
|
||||
def get_selectors(rules, credentials):
|
||||
statements = []
|
||||
for rule in rules:
|
||||
selector = json.dumps(rule.selector)
|
||||
if rule.action == "stop":
|
||||
statements.append(
|
||||
f"""var elem = document.querySelector({selector}); if (elem) {{ return; }}"""
|
||||
)
|
||||
elif rule.fill:
|
||||
value = json.dumps(getattr(credentials, rule.fill, None))
|
||||
if value:
|
||||
statements.append(
|
||||
f"""var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.value = {value}; elem.dispatchEvent(new Event("blur")); }}"""
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Credential info not available",
|
||||
type=rule.fill,
|
||||
possibilities=dir(credentials),
|
||||
)
|
||||
elif rule.action == "click":
|
||||
statements.append(
|
||||
f"""var elem = document.querySelector({selector}); if (elem) {{ elem.dispatchEvent(new Event("focus")); elem.click(); }}"""
|
||||
)
|
||||
return "\n".join(statements)
|
||||
PY
|
||||
|
||||
LEMANA_VPN_WEBENGINE_PROCESS="$fake_webengine" \
|
||||
LEMANA_VPN_OC_PYTHON=python3 \
|
||||
LEMANA_VPN_PATCH_BACKUP_DIR="$TMP_DIR/patch-backups" \
|
||||
bash "$ROOT/bin/vpn-lemanapro.sh" --patch-only >/dev/null
|
||||
|
||||
grep -q '"offscreen"' "$fake_webengine"
|
||||
grep -q 'LEMANA_VPN_AUTOFILL_DISABLE' "$fake_webengine"
|
||||
grep -q 'new RegExp' "$fake_webengine"
|
||||
grep -q 'script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)' "$fake_webengine"
|
||||
grep -q 'new Event("input", {{bubbles: true}})' "$fake_webengine"
|
||||
if grep -q 'ScriptWorldId.MainWorld' "$fake_webengine"; then
|
||||
echo "patched auto-fill should keep the original ApplicationWorld behavior" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -q '__lemanaVpnClicked' "$fake_webengine"; then
|
||||
echo "patched auto-fill should stay stateless like the original working setup" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -q 'valueSetter' "$fake_webengine"; then
|
||||
echo "patched auto-fill should use the original direct value assignment with input/change events" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
status_text="$(bash "$ROOT/bin/vpn-lemanapro.sh" --status)"
|
||||
printf '%s\n' "$status_text" | grep -q 'Modules:'
|
||||
|
||||
Reference in New Issue
Block a user