document.addEventListener('DOMContentLoaded', () => {
// DOM Elements
const form = document.getElementById('urlForm');
const hostnameInput = document.getElementById('hostname');
const domainSelect = document.getElementById('domain');
const portInput = document.getElementById('port');
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
const includeAuthCheck = document.getElementById('includeAuthCheck');
const authStatusText = document.getElementById('authStatusText');
const credentialsFields = document.getElementById('credentialsFields');
const preventSaveCheck = document.getElementById('preventPasswordSave');
const togglePasswordBtn = document.getElementById('togglePasswordVisibility');
const hostnameFeedback = document.getElementById('hostnameFeedback');
const urlDisplay = document.getElementById('urlDisplay');
const launchBtn = document.getElementById('launchBtn');
const copyBtn = document.getElementById('copyBtn');
const copyBtnText = document.getElementById('copyBtnText');
const autoOpenNotice = document.getElementById('autoOpenNotice');
// Validation patterns: IPv4, Router Serial (alphanumeric 10-14 chars), and FQDN
const serialRegex = /^[a-zA-Z0-9]{10,14}$/;
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
const domainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
let currentWinboxUri = '';
// Validate and categorize user input
function validateHostname(raw) {
let clean = raw.trim();
let port = null;
// Extract port if typed directly into host field (e.g., 10.0.0.1:8292)
const portMatch = clean.match(/:(\d{1,5})$/);
if (portMatch) {
port = portMatch[1];
clean = clean.replace(/:\d{1,5}$/, '');
}
if (!clean) {
hostnameFeedback.innerHTML = '';
return { type: 'empty', value: '', port };
}
if (ipv4Regex.test(clean)) {
hostnameFeedback.innerHTML = 'IPv4 Address';
return { type: 'ipv4', value: clean, port };
}
if (serialRegex.test(clean)) {
hostnameFeedback.innerHTML = 'Router Serial';
return { type: 'serial', value: clean, port };
}
if (domainRegex.test(clean)) {
hostnameFeedback.innerHTML = 'Domain / FQDN';
return { type: 'domain', value: clean, port };
}
hostnameFeedback.innerHTML = 'Unrecognized Format';
return { type: 'raw', value: clean, port };
}
// Convert router serial into MikroTik cloud DDNS or VPN hostname
function resolveHost(value, type) {
if (type === 'serial') {
return domainSelect.value === 'vpn'
? `${value.toLowerCase()}.mt.vpn.sn`
: `${value.toLowerCase()}.sn.mynetname.net`;
}
return value;
}
// Build the winbox:// URI and update the UI
function updateUi() {
const val = validateHostname(hostnameInput.value);
if (val.port) {
portInput.value = val.port;
}
const host = resolveHost(val.value, val.type);
const port = portInput.value.trim();
const includeAuth = includeAuthCheck.checked;
const username = includeAuth ? usernameInput.value.trim() : '';
const password = includeAuth ? passwordInput.value : '';
if (!host) {
currentWinboxUri = '';
urlDisplay.textContent = 'winbox://';
launchBtn.disabled = true;
launchBtn.classList.add('opacity-50', 'cursor-not-allowed');
return;
}
launchBtn.disabled = false;
launchBtn.classList.remove('opacity-50', 'cursor-not-allowed');
let uri = 'winbox://';
let displayUri = 'winbox://';
if (username && password) {
uri += `${encodeURIComponent(username)}:${encodeURIComponent(password)}@`;
displayUri += `${encodeURIComponent(username)}:••••••••@`;
} else if (username) {
uri += `${encodeURIComponent(username)}@`;
displayUri += `${encodeURIComponent(username)}@`;
}
uri += host;
displayUri += host;
if (port && port !== '8291') {
uri += `:${port}`;
displayUri += `:${port}`;
}
currentWinboxUri = uri;
urlDisplay.textContent = displayUri;
}
// Launch action
function openWinbox() {
if (!currentWinboxUri) return;
window.location.href = currentWinboxUri;
}
// Form submission
form.addEventListener('submit', (e) => {
e.preventDefault();
openWinbox();
});
// Copy to clipboard
copyBtn.addEventListener('click', async () => {
if (!currentWinboxUri) return;
try {
await navigator.clipboard.writeText(currentWinboxUri);
copyBtnText.textContent = 'Copied!';
setTimeout(() => { copyBtnText.textContent = 'Copy URI'; }, 1800);
} catch {
prompt('Copy WinBox URI:', currentWinboxUri);
}
});
// Password visibility toggle
togglePasswordBtn.addEventListener('click', () => {
const isPassword = passwordInput.type === 'password';
passwordInput.type = isPassword ? 'text' : 'password';
togglePasswordBtn.innerHTML = isPassword
? ``
: ``;
});
// Toggle credentials block on/off
includeAuthCheck.addEventListener('change', () => {
const enabled = includeAuthCheck.checked;
authStatusText.textContent = enabled ? 'Include' : 'Omit';
credentialsFields.classList.toggle('disabled-state', !enabled);
updateUi();
});
// Disallow browser password save prompt
preventSaveCheck.addEventListener('change', () => {
if (preventSaveCheck.checked) {
passwordInput.setAttribute('autocomplete', 'new-password');
usernameInput.setAttribute('autocomplete', 'off');
} else {
passwordInput.setAttribute('autocomplete', 'current-password');
usernameInput.setAttribute('autocomplete', 'username');
}
});
// Handle incoming URL parameters (e.g., ?hostname=192.168.88.1&user=admin&autoclose=1)
function handleQueryParams() {
const params = new URLSearchParams(window.location.search);
const hostParam = params.get('hostname') || params.get('host');
if (!hostParam) return;
hostnameInput.value = hostParam;
if (params.get('port')) portInput.value = params.get('port');
if (params.get('username') || params.get('user')) usernameInput.value = params.get('username') || params.get('user');
if (params.get('password') || params.get('pass')) passwordInput.value = params.get('password') || params.get('pass');
if (params.get('domain')) domainSelect.value = params.get('domain');
updateUi();
if (currentWinboxUri) {
openWinbox();
autoOpenNotice.classList.remove('hidden');
if (params.get('autoclose') === '1') {
setTimeout(() => {
window.close();
}, 500);
}
}
}
// Input listeners
[hostnameInput, domainSelect, portInput, usernameInput, passwordInput].forEach(el => {
el.addEventListener('input', updateUi);
});
// Initialization
updateUi();
handleQueryParams();
});