feat(clickTtHttpPageRoutes, ClickTtView): implement proxy navigation script and enhance URL selection

- Added a new function to inject a navigation script into HTML responses, enabling seamless proxy navigation for links and form submissions.
- Updated the ClickTtView component to include new preset URL options for HTTV and TTDE login, improving user experience by providing quick access to common links.
- Adjusted form handling logic to support the new preset URLs, ensuring proper URL management during proxy interactions.
This commit is contained in:
Torsten Schulz (local)
2026-03-10 22:48:54 +01:00
parent 71ac054d48
commit 59d7c3559c
2 changed files with 95 additions and 13 deletions

View File

@@ -159,6 +159,86 @@ function rewriteFormActionsInHtml(html, proxyBaseUrl, pageBaseUrl, sid) {
}
}
function injectProxyNavigationScript(html, proxyBaseUrl, pageBaseUrl, sid) {
if (!html || !proxyBaseUrl || !pageBaseUrl) return html;
const script = `
<script>
(function () {
const PROXY_BASE_URL = ${JSON.stringify(proxyBaseUrl)};
const PAGE_BASE_URL = ${JSON.stringify(pageBaseUrl)};
const SID = ${JSON.stringify(sid || '')};
function normalizeUrl(value, base) {
if (!value) return null;
const trimmed = String(value).trim();
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('javascript:')) return null;
try {
return new URL(trimmed, base || PAGE_BASE_URL).href;
} catch {
return null;
}
}
function shouldProxyUrl(value) {
return /(^|\\.)click-tt\\.de$|(^|\\.)httv\\.de$/i.test(new URL(value).hostname);
}
function buildProxyUrl(targetUrl) {
const separator = PROXY_BASE_URL.includes('?') ? '&' : '?';
return PROXY_BASE_URL + separator + 'url=' + encodeURIComponent(targetUrl) + (SID ? '&sid=' + encodeURIComponent(SID) : '');
}
function navigateViaProxy(targetUrl) {
if (!targetUrl) return false;
if (!shouldProxyUrl(targetUrl)) return false;
window.location.assign(buildProxyUrl(targetUrl));
return true;
}
function getSubmitTarget(form, submitter) {
const action = submitter && submitter.getAttribute && submitter.getAttribute('formaction');
if (action) return normalizeUrl(action, PAGE_BASE_URL);
return normalizeUrl(form.getAttribute('action') || PAGE_BASE_URL, PAGE_BASE_URL);
}
document.addEventListener('click', function (event) {
const anchor = event.target && event.target.closest ? event.target.closest('a[href]') : null;
if (!anchor) return;
const targetUrl = normalizeUrl(anchor.getAttribute('href'), PAGE_BASE_URL);
if (!targetUrl || !shouldProxyUrl(targetUrl)) return;
event.preventDefault();
event.stopPropagation();
navigateViaProxy(targetUrl);
}, true);
document.addEventListener('submit', function (event) {
const form = event.target;
if (!form || !form.tagName || form.tagName.toLowerCase() !== 'form') return;
const submitter = event.submitter || null;
const targetUrl = getSubmitTarget(form, submitter);
if (!targetUrl || !shouldProxyUrl(targetUrl)) return;
form.setAttribute('action', buildProxyUrl(targetUrl));
}, true);
const nativeSubmit = HTMLFormElement.prototype.submit;
HTMLFormElement.prototype.submit = function patchedSubmit() {
const targetUrl = getSubmitTarget(this, null);
if (targetUrl && shouldProxyUrl(targetUrl)) {
this.setAttribute('action', buildProxyUrl(targetUrl));
}
return nativeSubmit.apply(this, arguments);
};
})();
</script>`;
if (/<\/body>/i.test(html)) {
return html.replace(/<\/body>/i, `${script}</body>`);
}
return html + script;
}
/**
* GET /api/clicktt/proxy
* Proxy für iframe-Einbettung liefert HTML direkt (ohne Auth, für iframe src).
@@ -248,6 +328,7 @@ router.get('/proxy', async (req, res, next) => {
html = rewriteLinksInHtml(html, proxyBase, effectivePageUrl, sid);
html = rewriteFormActionsInHtml(html, proxyBase, effectivePageUrl, sid);
html = rewriteMetaRefreshInHtml(html, proxyBase, effectivePageUrl, sid);
html = injectProxyNavigationScript(html, proxyBase, effectivePageUrl, sid);
res.set({
'Content-Type': 'text/html; charset=utf-8',
@@ -344,6 +425,7 @@ router.post('/proxy', async (req, res, next) => {
responseBody = rewriteLinksInHtml(responseBody, proxyBase, effectivePageUrl, sid);
responseBody = rewriteFormActionsInHtml(responseBody, proxyBase, effectivePageUrl, sid);
responseBody = rewriteMetaRefreshInHtml(responseBody, proxyBase, effectivePageUrl, sid);
responseBody = injectProxyNavigationScript(responseBody, proxyBase, effectivePageUrl, sid);
}
res.set({

View File

@@ -17,10 +17,12 @@
<option value="clubInfo">Vereinsinfo (clubInfoDisplay)</option>
<option value="regionMeetings">Regionsspielplan (regionMeetingFilter)</option>
<option value="url">Direkte URL</option>
<option value="preset:httv-home">HTTV Einstieg: https://httv.de</option>
<option value="preset:ttde-login">TTDE Login: ttde-id.liga.nu/oauth2/authz/ttde...</option>
</select>
</div>
<div class="form-group" v-if="pageType !== 'url'">
<div class="form-group" v-if="pageType === 'leaguePage' || pageType === 'clubInfo' || pageType === 'regionMeetings'">
<label>Verband</label>
<select v-model="association" class="form-select">
<option value="HeTTV">HeTTV (Hessen)</option>
@@ -30,7 +32,7 @@
</select>
</div>
<div class="form-group" v-if="pageType !== 'url' && pageType !== 'clubInfo'">
<div class="form-group" v-if="pageType === 'leaguePage' || pageType === 'regionMeetings'">
<label>Championship / Saison</label>
<input
v-model="championship"
@@ -50,17 +52,6 @@
/>
</div>
<div class="form-group" v-if="pageType === 'url'">
<label>URL-Vorlage</label>
<select v-model="directUrl" class="form-select form-input-wide">
<option value="">Bitte auswählen...</option>
<option value="https://httv.de">HTTV Einstieg: https://httv.de</option>
<option value="https://ttde-id.liga.nu/oauth2/authz/ttde?scope=nuLiga&response_type=code&redirect_uri=https%3A%2F%2Fhttv.click-tt.de%2Fcgi-bin%2FWebObjects%2FnuLigaTTDE.woa%2Fwa%2FoAuthLogin&state=nonce%3DVF6WbXUOvTjpsGq9zoZ6oxTH7625JEGH&client_id=XtVpGjXKAhz3BZuu">
TTDE Login: ttde-id.liga.nu/oauth2/authz/ttde...
</option>
</select>
</div>
<div class="form-group" v-if="pageType === 'url'">
<label>URL</label>
<input
@@ -98,6 +89,10 @@
import { ref, computed } from 'vue';
const backendBaseUrl = import.meta.env.VITE_BACKEND || 'http://localhost:3005';
const presetUrls = {
'preset:httv-home': 'https://httv.de',
'preset:ttde-login': 'https://ttde-id.liga.nu/oauth2/authz/ttde?scope=nuLiga&response_type=code&redirect_uri=https%3A%2F%2Fhttv.click-tt.de%2Fcgi-bin%2FWebObjects%2FnuLigaTTDE.woa%2Fwa%2FoAuthLogin&state=nonce%3DVF6WbXUOvTjpsGq9zoZ6oxTH7625JEGH&client_id=XtVpGjXKAhz3BZuu',
};
export default {
name: 'ClickTtView',
@@ -114,6 +109,9 @@ export default {
if (pageType.value === 'url') {
return directUrl.value.trim().length > 0;
}
if (pageType.value.startsWith('preset:')) {
return true;
}
if (pageType.value === 'clubInfo') {
return clubId.value.trim().length > 0;
}
@@ -126,6 +124,8 @@ export default {
if (pageType.value === 'url') {
params.set('url', directUrl.value.trim());
} else if (pageType.value.startsWith('preset:')) {
params.set('url', presetUrls[pageType.value]);
} else {
params.set('type', pageType.value);
params.set('association', association.value);