|
|
|
|
@@ -22,6 +22,7 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
|
|
|
<meta name="theme-color" content="#1a1a2e">
|
|
|
|
|
<link rel="manifest" href="/manifest.json">
|
|
|
|
|
<link rel="icon" type="image/png" href="data:image/png;base64,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">
|
|
|
|
|
<style>
|
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
|
body {
|
|
|
|
|
@@ -74,7 +75,7 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.2);
|
|
|
|
|
}
|
|
|
|
|
.login-form input::placeholder { color: #666; }
|
|
|
|
|
.login-form button {
|
|
|
|
|
.login-form > button {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 14px;
|
|
|
|
|
border: none;
|
|
|
|
|
@@ -88,12 +89,80 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 1px;
|
|
|
|
|
}
|
|
|
|
|
.login-form button:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); }
|
|
|
|
|
.login-form button:disabled { background: #444; cursor: not-allowed; transform: none; box-shadow: none; }
|
|
|
|
|
.login-form > button:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); }
|
|
|
|
|
.login-form > button:disabled { background: #444; cursor: not-allowed; transform: none; box-shadow: none; }
|
|
|
|
|
.error-msg { color: #e94560; text-align: center; margin-top: 16px; font-size: 14px; }
|
|
|
|
|
.conn-status { text-align: center; margin-bottom: 20px; font-size: 13px; color: #666; }
|
|
|
|
|
.conn-status.connected { color: #4caf50; }
|
|
|
|
|
.conn-status.disconnected { color: #f44336; }
|
|
|
|
|
/* Password input with toggle */
|
|
|
|
|
.password-wrapper {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
.password-wrapper input {
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
padding-right: 48px;
|
|
|
|
|
}
|
|
|
|
|
.password-toggle {
|
|
|
|
|
position: absolute;
|
|
|
|
|
right: 12px;
|
|
|
|
|
top: 50%;
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
color: #888;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
padding: 0;
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
line-height: 24px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
transition: color 0.2s;
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
.password-toggle:hover { color: #e94560; opacity: 1; }
|
|
|
|
|
/* Login footer */
|
|
|
|
|
.login-footer {
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
padding-top: 20px;
|
|
|
|
|
border-top: 1px solid rgba(255,255,255,0.1);
|
|
|
|
|
}
|
|
|
|
|
.security-notice {
|
|
|
|
|
background: rgba(255,152,0,0.1);
|
|
|
|
|
border: 1px solid rgba(255,152,0,0.3);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #ccc;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
.security-notice strong {
|
|
|
|
|
color: #ff9800;
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
.login-links {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 24px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
.login-links a {
|
|
|
|
|
color: #e94560;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
transition: color 0.2s;
|
|
|
|
|
}
|
|
|
|
|
.login-links a:hover { color: #ff6b8a; }
|
|
|
|
|
)HTML";
|
|
|
|
|
|
|
|
|
|
// Part 2: Device page styles
|
|
|
|
|
@@ -711,9 +780,22 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
<h1>SimpleRemoter</h1>
|
|
|
|
|
<div id="ws-status" class="conn-status disconnected">Connecting...</div>
|
|
|
|
|
<input type="text" id="username" placeholder="Username" value="admin">
|
|
|
|
|
<input type="password" id="password" placeholder="Password">
|
|
|
|
|
<div class="password-wrapper">
|
|
|
|
|
<input type="password" id="password" placeholder="Password">
|
|
|
|
|
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()" title="Show/Hide Password">👁</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button id="login-btn" onclick="login()" disabled>Login</button>
|
|
|
|
|
<div id="login-error" class="error-msg"></div>
|
|
|
|
|
<div class="login-footer">
|
|
|
|
|
<div class="security-notice">
|
|
|
|
|
<strong>⚠ Security Notice</strong>
|
|
|
|
|
SimpleRemoter.com may be flagged as "dangerous" by browsers due to the word "Remote" in its name. This is a false positive. The software is fully open-source and safe to use.
|
|
|
|
|
</div>
|
|
|
|
|
<div class="login-links">
|
|
|
|
|
<a href="https://simpleremoter.com/" target="_blank">🌐 Website</a>
|
|
|
|
|
<a href="https://git.simpleremoter.com/" target="_blank">📦 Source Code</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="devices-page" class="page">
|
|
|
|
|
@@ -903,8 +985,16 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
startPingInterval();
|
|
|
|
|
// Auto-restore session if token exists
|
|
|
|
|
if (token) {
|
|
|
|
|
showPage('devices-page');
|
|
|
|
|
getDevices();
|
|
|
|
|
// Check if we were on screen-page with an active device
|
|
|
|
|
const screenPage = document.getElementById('screen-page');
|
|
|
|
|
if (screenPage.classList.contains('active') && currentDevice) {
|
|
|
|
|
// Reconnect to current device
|
|
|
|
|
updateScreenStatus('connecting');
|
|
|
|
|
ws.send(JSON.stringify({ cmd: 'connect', id: String(currentDevice.id), token }));
|
|
|
|
|
} else {
|
|
|
|
|
showPage('devices-page');
|
|
|
|
|
getDevices();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
ws.onclose = () => { stopPingInterval(); updateWsStatus('disconnected'); scheduleReconnect(); };
|
|
|
|
|
@@ -976,17 +1066,17 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'disconnect_result':
|
|
|
|
|
// Only navigate if authenticated
|
|
|
|
|
if (!token) break;
|
|
|
|
|
showPage('devices-page');
|
|
|
|
|
getDevices();
|
|
|
|
|
// disconnect() already handles navigation, this is just server acknowledgment
|
|
|
|
|
// No action needed - prevents race conditions when switching devices
|
|
|
|
|
break;
|
|
|
|
|
case 'resolution_changed':
|
|
|
|
|
updateScreenStatus('connected');
|
|
|
|
|
initDecoder(msg.width, msg.height);
|
|
|
|
|
break;
|
|
|
|
|
case 'device_offline':
|
|
|
|
|
// Only handle if this is the device we're currently viewing
|
|
|
|
|
if (!token) break;
|
|
|
|
|
if (!currentDevice || String(msg.id) !== String(currentDevice.id)) break;
|
|
|
|
|
updateScreenStatus('error', 'Device offline');
|
|
|
|
|
setTimeout(() => { showPage('devices-page'); getDevices(); }, 2000);
|
|
|
|
|
break;
|
|
|
|
|
@@ -1269,6 +1359,18 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
else el.textContent = msg || 'Error';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function togglePasswordVisibility() {
|
|
|
|
|
const input = document.getElementById('password');
|
|
|
|
|
const btn = document.querySelector('.password-toggle');
|
|
|
|
|
if (input.type === 'password') {
|
|
|
|
|
input.type = 'text';
|
|
|
|
|
btn.innerHTML = '👀'; // Eyes
|
|
|
|
|
} else {
|
|
|
|
|
input.type = 'password';
|
|
|
|
|
btn.innerHTML = '👁'; // Eye
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function login() {
|
|
|
|
|
const username = document.getElementById('username').value;
|
|
|
|
|
const password = document.getElementById('password').value;
|
|
|
|
|
@@ -2239,11 +2341,7 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
sendMouse('up', pos.x, pos.y, e.button);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
canvas.addEventListener('dblclick', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const pos = getMousePos(e);
|
|
|
|
|
sendMouse('dblclick', pos.x, pos.y, e.button);
|
|
|
|
|
});
|
|
|
|
|
// Note: dblclick is handled by mousedown-mouseup sequence, no separate handler needed
|
|
|
|
|
|
|
|
|
|
canvas.addEventListener('mousemove', function(e) {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
@@ -2366,7 +2464,10 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
canvas.style.transformOrigin = '';
|
|
|
|
|
|
|
|
|
|
if (decoder) { try { decoder.close(); } catch(e) {} decoder = null; }
|
|
|
|
|
if (ws && ws.readyState === WebSocket.OPEN && token) ws.send(JSON.stringify({ cmd: 'disconnect', token }));
|
|
|
|
|
if (ws && ws.readyState === WebSocket.OPEN && token && currentDevice) {
|
|
|
|
|
ws.send(JSON.stringify({ cmd: 'disconnect', token, id: String(currentDevice.id) }));
|
|
|
|
|
}
|
|
|
|
|
currentDevice = null; // Clear current device
|
|
|
|
|
showPage('devices-page');
|
|
|
|
|
getDevices();
|
|
|
|
|
}
|
|
|
|
|
@@ -2392,22 +2493,49 @@ inline std::string GetWebPageHTML() {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Handle page visibility change (iOS PWA background/foreground)
|
|
|
|
|
let backgroundDisconnectTimer = null;
|
|
|
|
|
const BACKGROUND_TIMEOUT_MOBILE = 30000; // 30s for mobile/tablet
|
|
|
|
|
|
|
|
|
|
function doBackgroundDisconnect() {
|
|
|
|
|
cancelReconnect();
|
|
|
|
|
stopPingInterval();
|
|
|
|
|
if (ws) {
|
|
|
|
|
ws.onclose = null;
|
|
|
|
|
ws.close();
|
|
|
|
|
ws = null;
|
|
|
|
|
}
|
|
|
|
|
updateWsStatus('disconnected');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.addEventListener('visibilitychange', () => {
|
|
|
|
|
isPageVisible = !document.hidden;
|
|
|
|
|
if (document.hidden) {
|
|
|
|
|
// Page going to background - close connection and cancel reconnect
|
|
|
|
|
cancelReconnect();
|
|
|
|
|
stopPingInterval();
|
|
|
|
|
if (ws) {
|
|
|
|
|
ws.onclose = null; // Prevent triggering reconnect
|
|
|
|
|
ws.close();
|
|
|
|
|
ws = null;
|
|
|
|
|
// Page going to background
|
|
|
|
|
const screenPage = document.getElementById('screen-page');
|
|
|
|
|
const onScreenPage = screenPage && screenPage.classList.contains('active') && currentDevice;
|
|
|
|
|
|
|
|
|
|
if (onScreenPage) {
|
|
|
|
|
// Mobile/tablet: delay disconnect 30s
|
|
|
|
|
// Desktop: keep connection alive (no timer)
|
|
|
|
|
if (isTouchDevice) {
|
|
|
|
|
backgroundDisconnectTimer = setTimeout(doBackgroundDisconnect, BACKGROUND_TIMEOUT_MOBILE);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Other pages - disconnect immediately
|
|
|
|
|
doBackgroundDisconnect();
|
|
|
|
|
}
|
|
|
|
|
updateWsStatus('disconnected');
|
|
|
|
|
} else {
|
|
|
|
|
// Page coming to foreground - reconnect
|
|
|
|
|
// Page coming to foreground
|
|
|
|
|
if (backgroundDisconnectTimer) {
|
|
|
|
|
clearTimeout(backgroundDisconnectTimer);
|
|
|
|
|
backgroundDisconnectTimer = null;
|
|
|
|
|
}
|
|
|
|
|
// Reconnect or send immediate ping
|
|
|
|
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
|
|
|
connectWebSocket();
|
|
|
|
|
} else if (token) {
|
|
|
|
|
// Connection still open - send immediate ping to refresh server heartbeat
|
|
|
|
|
ws.send(JSON.stringify({ cmd: 'ping', token }));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|