fixes
This commit is contained in:
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.16"
|
version = "0.2.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.16"
|
version = "0.2.17"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
+1
-42
@@ -33,14 +33,6 @@ struct ClientDiagnostics {
|
|||||||
timezone: Option<String>,
|
timezone: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
platform: Option<String>,
|
platform: Option<String>,
|
||||||
#[serde(default)]
|
|
||||||
effective_type: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
downlink_mbps: Option<f64>,
|
|
||||||
#[serde(default)]
|
|
||||||
network_rtt_ms: Option<u64>,
|
|
||||||
#[serde(default)]
|
|
||||||
save_data: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -48,7 +40,6 @@ struct RequestClientContext {
|
|||||||
client_id: String,
|
client_id: String,
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
accept_language: Option<String>,
|
accept_language: Option<String>,
|
||||||
request_scheme: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestClientContext {
|
impl RequestClientContext {
|
||||||
@@ -58,13 +49,6 @@ impl RequestClientContext {
|
|||||||
client_id: hex::encode(&digest[..8]),
|
client_id: hex::encode(&digest[..8]),
|
||||||
user_agent: diagnostic_header(headers, header::USER_AGENT.as_str(), 180),
|
user_agent: diagnostic_header(headers, header::USER_AGENT.as_str(), 180),
|
||||||
accept_language: diagnostic_header(headers, header::ACCEPT_LANGUAGE.as_str(), 100),
|
accept_language: diagnostic_header(headers, header::ACCEPT_LANGUAGE.as_str(), 100),
|
||||||
request_scheme: diagnostic_header(headers, "x-forwarded-proto", 12).or_else(|| {
|
|
||||||
headers
|
|
||||||
.get(header::ORIGIN)
|
|
||||||
.and_then(|value| value.to_str().ok())
|
|
||||||
.and_then(|value| value.split_once("://").map(|(scheme, _)| scheme))
|
|
||||||
.and_then(|value| clean_diagnostic_text(Some(value.to_owned()), 12))
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,23 +65,13 @@ struct DiagnosticClient {
|
|||||||
id: String,
|
id: String,
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
accept_language: Option<String>,
|
accept_language: Option<String>,
|
||||||
request_scheme: Option<String>,
|
|
||||||
language: Option<String>,
|
language: Option<String>,
|
||||||
timezone: Option<String>,
|
timezone: Option<String>,
|
||||||
platform: Option<String>,
|
platform: Option<String>,
|
||||||
effective_type: Option<String>,
|
|
||||||
downlink_mbps: Option<f64>,
|
|
||||||
network_rtt_ms: Option<u64>,
|
|
||||||
save_data: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct ConnectionDiagnostics {
|
struct ConnectionDiagnostics {
|
||||||
connection_id: u64,
|
|
||||||
connected_at: String,
|
|
||||||
server_time: String,
|
|
||||||
server_version: &'static str,
|
|
||||||
transport: &'static str,
|
|
||||||
heartbeat_interval_ms: u64,
|
heartbeat_interval_ms: u64,
|
||||||
heartbeat_timeout_ms: u64,
|
heartbeat_timeout_ms: u64,
|
||||||
max_reconnect_delay_ms: u64,
|
max_reconnect_delay_ms: u64,
|
||||||
@@ -169,18 +143,11 @@ enum ServerMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn connection_diagnostics(
|
fn connection_diagnostics(
|
||||||
connection_id: u64,
|
|
||||||
request: &RequestClientContext,
|
request: &RequestClientContext,
|
||||||
client: Option<ClientDiagnostics>,
|
client: Option<ClientDiagnostics>,
|
||||||
) -> ConnectionDiagnostics {
|
) -> ConnectionDiagnostics {
|
||||||
let client = client.unwrap_or_default();
|
let client = client.unwrap_or_default();
|
||||||
let now = chrono::Utc::now().to_rfc3339();
|
|
||||||
ConnectionDiagnostics {
|
ConnectionDiagnostics {
|
||||||
connection_id,
|
|
||||||
connected_at: now.clone(),
|
|
||||||
server_time: now,
|
|
||||||
server_version: env!("CARGO_PKG_VERSION"),
|
|
||||||
transport: "websocket",
|
|
||||||
heartbeat_interval_ms: HEARTBEAT_INTERVAL_MS,
|
heartbeat_interval_ms: HEARTBEAT_INTERVAL_MS,
|
||||||
heartbeat_timeout_ms: HEARTBEAT_TIMEOUT_MS,
|
heartbeat_timeout_ms: HEARTBEAT_TIMEOUT_MS,
|
||||||
max_reconnect_delay_ms: MAX_RECONNECT_DELAY_MS,
|
max_reconnect_delay_ms: MAX_RECONNECT_DELAY_MS,
|
||||||
@@ -194,17 +161,9 @@ fn connection_diagnostics(
|
|||||||
id: request.client_id.clone(),
|
id: request.client_id.clone(),
|
||||||
user_agent: request.user_agent.clone(),
|
user_agent: request.user_agent.clone(),
|
||||||
accept_language: request.accept_language.clone(),
|
accept_language: request.accept_language.clone(),
|
||||||
request_scheme: request.request_scheme.clone(),
|
|
||||||
language: clean_diagnostic_text(client.language, 40),
|
language: clean_diagnostic_text(client.language, 40),
|
||||||
timezone: clean_diagnostic_text(client.timezone, 80),
|
timezone: clean_diagnostic_text(client.timezone, 80),
|
||||||
platform: clean_diagnostic_text(client.platform, 80),
|
platform: clean_diagnostic_text(client.platform, 80),
|
||||||
effective_type: clean_diagnostic_text(client.effective_type, 20),
|
|
||||||
downlink_mbps: client
|
|
||||||
.downlink_mbps
|
|
||||||
.filter(|value| value.is_finite())
|
|
||||||
.map(|value| value.clamp(0.0, 10_000.0)),
|
|
||||||
network_rtt_ms: client.network_rtt_ms.map(|value| value.min(120_000)),
|
|
||||||
save_data: client.save_data,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,7 +447,7 @@ async fn handle_socket(
|
|||||||
if send_split(
|
if send_split(
|
||||||
&mut sender,
|
&mut sender,
|
||||||
&ServerMessage::Diagnostics {
|
&ServerMessage::Diagnostics {
|
||||||
diagnostics: connection_diagnostics(connection_id, &client_context, client_diagnostics),
|
diagnostics: connection_diagnostics(&client_context, client_diagnostics),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ async fn handle_pad_socket(
|
|||||||
if send_pad_split(
|
if send_pad_split(
|
||||||
&mut sender,
|
&mut sender,
|
||||||
&PadServerMessage::Diagnostics {
|
&PadServerMessage::Diagnostics {
|
||||||
diagnostics: connection_diagnostics(connection_id, &client_context, client_diagnostics),
|
diagnostics: connection_diagnostics(&client_context, client_diagnostics),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -2809,7 +2809,7 @@ dialog::backdrop {
|
|||||||
z-index: 55;
|
z-index: 55;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: calc(100% + 9px);
|
bottom: calc(100% + 9px);
|
||||||
width: min(430px, calc(100vw - 24px));
|
width: min(400px, calc(100vw - 24px));
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: 1px solid var(--border-strong);
|
border: 1px solid var(--border-strong);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -5297,7 +5297,7 @@ dialog::backdrop {
|
|||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: calc(64px + env(safe-area-inset-bottom, 0px));
|
bottom: calc(64px + env(safe-area-inset-bottom, 0px));
|
||||||
left: auto;
|
left: auto;
|
||||||
width: min(430px, calc(100vw - 24px));
|
width: min(400px, calc(100vw - 24px));
|
||||||
max-height: calc(100dvh - 88px);
|
max-height: calc(100dvh - 88px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
|
|||||||
+6
-38
@@ -173,7 +173,7 @@
|
|||||||
<div class="connection-diagnostics-popover">
|
<div class="connection-diagnostics-popover">
|
||||||
<strong>Connection diagnostics</strong>
|
<strong>Connection diagnostics</strong>
|
||||||
<dl class="connection-diagnostics-grid">
|
<dl class="connection-diagnostics-grid">
|
||||||
<div>
|
<div class="connection-diagnostics-grid__wide">
|
||||||
<dt>Quality</dt>
|
<dt>Quality</dt>
|
||||||
<dd data-connection-diagnostic="quality">Waiting</dd>
|
<dd data-connection-diagnostic="quality">Waiting</dd>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,26 +193,10 @@
|
|||||||
<dt>Reconnects</dt>
|
<dt>Reconnects</dt>
|
||||||
<dd data-connection-diagnostic="reconnects">0</dd>
|
<dd data-connection-diagnostic="reconnects">0</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="connection-diagnostics-grid__wide">
|
||||||
<dt>Transport</dt>
|
|
||||||
<dd data-connection-diagnostic="transport">WebSocket</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>Heartbeat</dt>
|
|
||||||
<dd data-connection-diagnostic="heartbeat">—</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>Network</dt>
|
|
||||||
<dd data-connection-diagnostic="network">—</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>Client</dt>
|
<dt>Client</dt>
|
||||||
<dd data-connection-diagnostic="client">—</dd>
|
<dd data-connection-diagnostic="client">—</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<dt>Server</dt>
|
|
||||||
<dd data-connection-diagnostic="server">—</dd>
|
|
||||||
</div>
|
|
||||||
<div class="connection-diagnostics-grid__wide">
|
<div class="connection-diagnostics-grid__wide">
|
||||||
<dt>Last event</dt>
|
<dt>Last event</dt>
|
||||||
<dd data-connection-diagnostic="last-event">—</dd>
|
<dd data-connection-diagnostic="last-event">—</dd>
|
||||||
@@ -292,9 +276,9 @@
|
|||||||
guest</button><button id="show-register" class="text-button" type="button">Register</button><button
|
guest</button><button id="show-register" class="text-button" type="button">Register</button><button
|
||||||
id="show-login" class="text-button" type="button">Log in</button></div>
|
id="show-login" class="text-button" type="button">Log in</button></div>
|
||||||
<section id="auth-panel" class="auth-panel" hidden>
|
<section id="auth-panel" class="auth-panel" hidden>
|
||||||
<h3 id="auth-mode-title">Log in</h3><label id="auth-email-field">E-mail / LDAP or AD Username<input
|
<h3 id="auth-mode-title">Log in</h3><label id="auth-email-field"><span id="auth-email-label">E-mail</span><input
|
||||||
id="auth-email" name="username" type="email" maxlength="320" autocomplete="username"
|
id="auth-email" name="username" type="email" maxlength="320" autocomplete="username"
|
||||||
placeholder="you@example.com or name.second-name"></label><label>Password<input
|
placeholder="you@example.com"></label><label>Password<input
|
||||||
id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
||||||
autocomplete="current-password"></label><button id="auth-submit" class="primary-button"
|
autocomplete="current-password"></label><button id="auth-submit" class="primary-button"
|
||||||
type="submit">Log in and continue</button>
|
type="submit">Log in and continue</button>
|
||||||
@@ -357,7 +341,7 @@
|
|||||||
<div class="connection-diagnostics-popover mobile-connection-diagnostics-popover">
|
<div class="connection-diagnostics-popover mobile-connection-diagnostics-popover">
|
||||||
<strong>Connection diagnostics</strong>
|
<strong>Connection diagnostics</strong>
|
||||||
<dl class="connection-diagnostics-grid">
|
<dl class="connection-diagnostics-grid">
|
||||||
<div>
|
<div class="connection-diagnostics-grid__wide">
|
||||||
<dt>Quality</dt>
|
<dt>Quality</dt>
|
||||||
<dd data-connection-diagnostic="quality">Waiting</dd>
|
<dd data-connection-diagnostic="quality">Waiting</dd>
|
||||||
</div>
|
</div>
|
||||||
@@ -377,26 +361,10 @@
|
|||||||
<dt>Reconnects</dt>
|
<dt>Reconnects</dt>
|
||||||
<dd data-connection-diagnostic="reconnects">0</dd>
|
<dd data-connection-diagnostic="reconnects">0</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="connection-diagnostics-grid__wide">
|
||||||
<dt>Transport</dt>
|
|
||||||
<dd data-connection-diagnostic="transport">WebSocket</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>Heartbeat</dt>
|
|
||||||
<dd data-connection-diagnostic="heartbeat">—</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>Network</dt>
|
|
||||||
<dd data-connection-diagnostic="network">—</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>Client</dt>
|
<dt>Client</dt>
|
||||||
<dd data-connection-diagnostic="client">—</dd>
|
<dd data-connection-diagnostic="client">—</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<dt>Server</dt>
|
|
||||||
<dd data-connection-diagnostic="server">—</dd>
|
|
||||||
</div>
|
|
||||||
<div class="connection-diagnostics-grid__wide">
|
<div class="connection-diagnostics-grid__wide">
|
||||||
<dt>Last event</dt>
|
<dt>Last event</dt>
|
||||||
<dd data-connection-diagnostic="last-event">—</dd>
|
<dd data-connection-diagnostic="last-event">—</dd>
|
||||||
|
|||||||
+2
-2
@@ -101,8 +101,8 @@
|
|||||||
<div class="identity-fields">
|
<div class="identity-fields">
|
||||||
<label>Nickname<input id="nickname" name="nickname" maxlength="40" autocomplete="off" data-bwignore="true"
|
<label>Nickname<input id="nickname" name="nickname" maxlength="40" autocomplete="off" data-bwignore="true"
|
||||||
placeholder="Your nickname"></label>
|
placeholder="Your nickname"></label>
|
||||||
<label id="auth-email-field">E-mail / LDAP or AD Username<input id="auth-email" name="username" type="email"
|
<label id="auth-email-field"><span id="auth-email-label">E-mail</span><input id="auth-email" name="username"
|
||||||
maxlength="320" autocomplete="username" required placeholder="you@example.com or name.second-name"></label>
|
type="email" maxlength="320" autocomplete="username" required placeholder="you@example.com"></label>
|
||||||
<label>Password<input id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
<label>Password<input id="auth-password" name="password" type="password" minlength="8" maxlength="128"
|
||||||
autocomplete="current-password" required placeholder="At least 8 characters"></label>
|
autocomplete="current-password" required placeholder="At least 8 characters"></label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+27
-25
@@ -20,6 +20,26 @@ const clearAuthSession = sessionStore.clearAuthSession || (() => {
|
|||||||
sessionStorage.removeItem("rustpad:nickname");
|
sessionStorage.removeItem("rustpad:nickname");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function configureCredentialFields({ emailLabel, email, password, loginMode, externalAuth }) {
|
||||||
|
const directoryLogin = loginMode && externalAuth;
|
||||||
|
emailLabel.textContent = directoryLogin ? "E-mail / LDAP or AD Username" : "E-mail";
|
||||||
|
email.type = directoryLogin ? "text" : "email";
|
||||||
|
email.placeholder = directoryLogin ? "you@example.com or username" : "you@example.com";
|
||||||
|
email.autocomplete = loginMode ? "username" : "off";
|
||||||
|
password.autocomplete = loginMode ? "current-password" : "off";
|
||||||
|
|
||||||
|
if (directoryLogin) password.removeAttribute("minlength");
|
||||||
|
else password.minLength = 8;
|
||||||
|
|
||||||
|
if (loginMode) {
|
||||||
|
email.removeAttribute("data-bwignore");
|
||||||
|
password.removeAttribute("data-bwignore");
|
||||||
|
} else {
|
||||||
|
email.dataset.bwignore = "true";
|
||||||
|
password.dataset.bwignore = "true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function bindIdentityDialog({ dialog, onIdentity, initialMode = "login" }) {
|
export function bindIdentityDialog({ dialog, onIdentity, initialMode = "login" }) {
|
||||||
if (dialog.querySelector("#auth-panel")) return bindLegacyIdentityDialog({ dialog, onIdentity });
|
if (dialog.querySelector("#auth-panel")) return bindLegacyIdentityDialog({ dialog, onIdentity });
|
||||||
const form = dialog.querySelector("#identity-form");
|
const form = dialog.querySelector("#identity-form");
|
||||||
@@ -28,6 +48,7 @@ export function bindIdentityDialog({ dialog, onIdentity, initialMode = "login" }
|
|||||||
const title = dialog.querySelector("#identity-title");
|
const title = dialog.querySelector("#identity-title");
|
||||||
const copy = dialog.querySelector("#identity-copy");
|
const copy = dialog.querySelector("#identity-copy");
|
||||||
const emailField = dialog.querySelector("#auth-email-field");
|
const emailField = dialog.querySelector("#auth-email-field");
|
||||||
|
const emailLabel = dialog.querySelector("#auth-email-label");
|
||||||
const email = dialog.querySelector("#auth-email");
|
const email = dialog.querySelector("#auth-email");
|
||||||
const password = dialog.querySelector("#auth-password");
|
const password = dialog.querySelector("#auth-password");
|
||||||
const submit = dialog.querySelector("#auth-submit");
|
const submit = dialog.querySelector("#auth-submit");
|
||||||
@@ -48,7 +69,9 @@ export function bindIdentityDialog({ dialog, onIdentity, initialMode = "login" }
|
|||||||
? "Enter the e-mail address assigned to your local account."
|
? "Enter the e-mail address assigned to your local account."
|
||||||
: registering
|
: registering
|
||||||
? "Register your account with an e-mail address and password."
|
? "Register your account with an e-mail address and password."
|
||||||
: "Use your e-mail address or organization login and password.";
|
: externalAuth
|
||||||
|
? "Use your e-mail address or LDAP/AD username and password."
|
||||||
|
: "Use your e-mail address and password.";
|
||||||
|
|
||||||
nickname.closest("label").hidden = !registering;
|
nickname.closest("label").hidden = !registering;
|
||||||
nickname.disabled = !registering;
|
nickname.disabled = !registering;
|
||||||
@@ -62,21 +85,10 @@ export function bindIdentityDialog({ dialog, onIdentity, initialMode = "login" }
|
|||||||
resetButton.hidden = resetting || registering || externalAuth;
|
resetButton.hidden = resetting || registering || externalAuth;
|
||||||
backButton.hidden = !resetting;
|
backButton.hidden = !resetting;
|
||||||
const loginMode = mode === "login";
|
const loginMode = mode === "login";
|
||||||
email.type = externalAuth && loginMode ? "text" : "email";
|
configureCredentialFields({ emailLabel, email, password, loginMode, externalAuth });
|
||||||
if (externalAuth && loginMode) password.removeAttribute("minlength");
|
|
||||||
else password.minLength = 8;
|
|
||||||
form.autocomplete = loginMode ? "on" : "off";
|
form.autocomplete = loginMode ? "on" : "off";
|
||||||
nickname.autocomplete = "off";
|
nickname.autocomplete = "off";
|
||||||
nickname.dataset.bwignore = "true";
|
nickname.dataset.bwignore = "true";
|
||||||
email.autocomplete = loginMode ? "username" : "off";
|
|
||||||
password.autocomplete = loginMode ? "current-password" : "off";
|
|
||||||
if (loginMode) {
|
|
||||||
email.removeAttribute("data-bwignore");
|
|
||||||
password.removeAttribute("data-bwignore");
|
|
||||||
} else {
|
|
||||||
email.dataset.bwignore = "true";
|
|
||||||
password.dataset.bwignore = "true";
|
|
||||||
}
|
|
||||||
message.textContent = "";
|
message.textContent = "";
|
||||||
|
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -168,6 +180,7 @@ function bindLegacyIdentityDialog({ dialog, onIdentity }) {
|
|||||||
const message = dialog.querySelector("#identity-error");
|
const message = dialog.querySelector("#identity-error");
|
||||||
const modeTitle = dialog.querySelector("#auth-mode-title");
|
const modeTitle = dialog.querySelector("#auth-mode-title");
|
||||||
const emailField = dialog.querySelector("#auth-email-field");
|
const emailField = dialog.querySelector("#auth-email-field");
|
||||||
|
const emailLabel = dialog.querySelector("#auth-email-label");
|
||||||
const email = dialog.querySelector("#auth-email");
|
const email = dialog.querySelector("#auth-email");
|
||||||
const password = dialog.querySelector("#auth-password");
|
const password = dialog.querySelector("#auth-password");
|
||||||
const authSubmit = dialog.querySelector("#auth-submit");
|
const authSubmit = dialog.querySelector("#auth-submit");
|
||||||
@@ -205,21 +218,10 @@ function bindLegacyIdentityDialog({ dialog, onIdentity }) {
|
|||||||
modeTitle.textContent = mode === "register" ? "Register" : "Log in";
|
modeTitle.textContent = mode === "register" ? "Register" : "Log in";
|
||||||
authSubmit.textContent = mode === "register" ? "Register and continue" : "Log in and continue";
|
authSubmit.textContent = mode === "register" ? "Register and continue" : "Log in and continue";
|
||||||
const loginMode = mode === "login";
|
const loginMode = mode === "login";
|
||||||
email.type = externalAuth && loginMode ? "text" : "email";
|
configureCredentialFields({ emailLabel, email, password, loginMode, externalAuth });
|
||||||
if (externalAuth && loginMode) password.removeAttribute("minlength");
|
|
||||||
else password.minLength = 8;
|
|
||||||
form.autocomplete = loginMode ? "on" : "off";
|
form.autocomplete = loginMode ? "on" : "off";
|
||||||
nickname.autocomplete = "off";
|
nickname.autocomplete = "off";
|
||||||
nickname.dataset.bwignore = "true";
|
nickname.dataset.bwignore = "true";
|
||||||
email.autocomplete = loginMode ? "username" : "off";
|
|
||||||
password.autocomplete = loginMode ? "current-password" : "off";
|
|
||||||
if (loginMode) {
|
|
||||||
email.removeAttribute("data-bwignore");
|
|
||||||
password.removeAttribute("data-bwignore");
|
|
||||||
} else {
|
|
||||||
email.dataset.bwignore = "true";
|
|
||||||
password.dataset.bwignore = "true";
|
|
||||||
}
|
|
||||||
message.textContent = "";
|
message.textContent = "";
|
||||||
queueMicrotask(() => (mode === "register" ? nickname : email).focus());
|
queueMicrotask(() => (mode === "register" ? nickname : email).focus());
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -249,20 +249,8 @@ export function startNoteEditor(adapter) {
|
|||||||
? formatDiagnosticDuration(runtime.uptime_ms)
|
? formatDiagnosticDuration(runtime.uptime_ms)
|
||||||
: runtime.last_connection_uptime_ms ? `last ${formatDiagnosticDuration(runtime.last_connection_uptime_ms)}` : "—");
|
: runtime.last_connection_uptime_ms ? `last ${formatDiagnosticDuration(runtime.last_connection_uptime_ms)}` : "—");
|
||||||
setDiagnosticField("reconnects", `${runtime.total_reconnects || 0}${runtime.reconnect_attempt ? ` · attempt ${runtime.reconnect_attempt}` : ""}`);
|
setDiagnosticField("reconnects", `${runtime.total_reconnects || 0}${runtime.reconnect_attempt ? ` · attempt ${runtime.reconnect_attempt}` : ""}`);
|
||||||
const scheme = client.request_scheme ? `${client.request_scheme.toUpperCase()} / ` : "";
|
|
||||||
setDiagnosticField("transport", `${scheme}${server.transport || "WebSocket"}`);
|
|
||||||
setDiagnosticField("heartbeat", server.heartbeat_interval_ms
|
|
||||||
? `${Math.round(server.heartbeat_interval_ms / 1000)}s ping · ${Math.round(server.heartbeat_timeout_ms / 1000)}s timeout`
|
|
||||||
: "Waiting for server policy");
|
|
||||||
const network = runtime.network || {};
|
|
||||||
const networkParts = [network.effective_type || client.effective_type];
|
|
||||||
if (Number.isFinite(network.downlink_mbps ?? client.downlink_mbps)) networkParts.push(`${network.downlink_mbps ?? client.downlink_mbps} Mb/s`);
|
|
||||||
if (Number.isFinite(network.rtt_ms ?? client.network_rtt_ms)) networkParts.push(`system RTT ${Math.round(network.rtt_ms ?? client.network_rtt_ms)} ms`);
|
|
||||||
if ((network.save_data ?? client.save_data) === true) networkParts.push("data saver");
|
|
||||||
setDiagnosticField("network", networkParts.filter(Boolean).join(" · ") || (runtime.online === false ? "Offline" : "Not exposed by browser"));
|
|
||||||
const clientParts = [client.platform, client.timezone, client.language || client.accept_language, client.id ? `id ${client.id}` : null, client.user_agent];
|
const clientParts = [client.platform, client.timezone, client.language || client.accept_language, client.id ? `id ${client.id}` : null, client.user_agent];
|
||||||
setDiagnosticField("client", clientParts.filter(Boolean).join(" · ") || "Waiting for server data");
|
setDiagnosticField("client", clientParts.filter(Boolean).join(" · ") || "Waiting for server data");
|
||||||
setDiagnosticField("server", server.server_version ? `RustPad ${server.server_version} · connection ${server.connection_id}` : "Waiting for server data");
|
|
||||||
const lastEvent = runtime.last_close
|
const lastEvent = runtime.last_close
|
||||||
? `Closed ${runtime.last_close.code}${runtime.last_close.reason ? `: ${runtime.last_close.reason}` : ""}`
|
? `Closed ${runtime.last_close.code}${runtime.last_close.reason ? `: ${runtime.last_close.reason}` : ""}`
|
||||||
: runtime.last_message_at ? `Message ${new Date(runtime.last_message_at).toLocaleTimeString()}` : "No messages yet";
|
: runtime.last_message_at ? `Message ${new Date(runtime.last_message_at).toLocaleTimeString()}` : "No messages yet";
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class RoomSocket {
|
|||||||
this.latencySampleWindow = DEFAULT_LATENCY_SAMPLE_WINDOW;
|
this.latencySampleWindow = DEFAULT_LATENCY_SAMPLE_WINDOW;
|
||||||
this.latencySamples = [];
|
this.latencySamples = [];
|
||||||
this.totalReconnects = 0;
|
this.totalReconnects = 0;
|
||||||
this.connectedAt = null;
|
|
||||||
this.authenticatedAt = null;
|
this.authenticatedAt = null;
|
||||||
this.lastConnectionUptimeMs = 0;
|
this.lastConnectionUptimeMs = 0;
|
||||||
this.lastMessageAt = null;
|
this.lastMessageAt = null;
|
||||||
@@ -51,15 +50,10 @@ class RoomSocket {
|
|||||||
get kind() { return "room"; }
|
get kind() { return "room"; }
|
||||||
|
|
||||||
clientDiagnostics() {
|
clientDiagnostics() {
|
||||||
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
||||||
return {
|
return {
|
||||||
language: navigator.language || null,
|
language: navigator.language || null,
|
||||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || null,
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || null,
|
||||||
platform: navigator.userAgentData?.platform || navigator.platform || null,
|
platform: navigator.userAgentData?.platform || navigator.platform || null,
|
||||||
effective_type: connection?.effectiveType || null,
|
|
||||||
downlink_mbps: Number.isFinite(connection?.downlink) ? connection.downlink : null,
|
|
||||||
network_rtt_ms: Number.isFinite(connection?.rtt) ? Math.max(0, Math.round(connection.rtt)) : null,
|
|
||||||
save_data: typeof connection?.saveData === "boolean" ? connection.saveData : null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +87,6 @@ class RoomSocket {
|
|||||||
socket.addEventListener("open", () => {
|
socket.addEventListener("open", () => {
|
||||||
if (socket !== this.socket || this.stopped) return;
|
if (socket !== this.socket || this.stopped) return;
|
||||||
logInfo("websocket.open", { kind: this.kind });
|
logInfo("websocket.open", { kind: this.kind });
|
||||||
this.connectedAt = Date.now();
|
|
||||||
this.serverDiagnostics = null;
|
this.serverDiagnostics = null;
|
||||||
this.lastClose = null;
|
this.lastClose = null;
|
||||||
this.send({
|
this.send({
|
||||||
@@ -161,7 +154,6 @@ class RoomSocket {
|
|||||||
this.onLatency?.(null);
|
this.onLatency?.(null);
|
||||||
const closedAt = Date.now();
|
const closedAt = Date.now();
|
||||||
this.lastConnectionUptimeMs = this.authenticatedAt ? Math.max(0, closedAt - this.authenticatedAt) : this.lastConnectionUptimeMs;
|
this.lastConnectionUptimeMs = this.authenticatedAt ? Math.max(0, closedAt - this.authenticatedAt) : this.lastConnectionUptimeMs;
|
||||||
this.connectedAt = null;
|
|
||||||
this.authenticatedAt = null;
|
this.authenticatedAt = null;
|
||||||
this.lastClose = { code: event.code, reason: event.reason || "", at: closedAt };
|
this.lastClose = { code: event.code, reason: event.reason || "", at: closedAt };
|
||||||
this.emitDiagnostics();
|
this.emitDiagnostics();
|
||||||
@@ -260,7 +252,6 @@ class RoomSocket {
|
|||||||
this.latencySampleWindow = Math.round(positiveNumber(diagnostics.latency_sample_window, this.latencySampleWindow, 3, 100));
|
this.latencySampleWindow = Math.round(positiveNumber(diagnostics.latency_sample_window, this.latencySampleWindow, 3, 100));
|
||||||
while (this.latencySamples.length > this.latencySampleWindow) this.latencySamples.shift();
|
while (this.latencySamples.length > this.latencySampleWindow) this.latencySamples.shift();
|
||||||
if (this.socket?.readyState === WebSocket.OPEN && this.authenticatedAt) this.startPing();
|
if (this.socket?.readyState === WebSocket.OPEN && this.authenticatedAt) this.startPing();
|
||||||
this.onServerDiagnostics?.(diagnostics);
|
|
||||||
this.emitDiagnostics();
|
this.emitDiagnostics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,31 +273,22 @@ class RoomSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emitDiagnostics() {
|
emitDiagnostics() {
|
||||||
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
||||||
const readyStates = ["connecting", "open", "closing", "closed"];
|
const readyStates = ["connecting", "open", "closing", "closed"];
|
||||||
this.onDiagnostics?.({
|
this.onDiagnostics?.({
|
||||||
server: this.serverDiagnostics,
|
server: this.serverDiagnostics,
|
||||||
runtime: {
|
runtime: {
|
||||||
state: readyStates[this.socket?.readyState ?? WebSocket.CLOSED] || "closed",
|
state: readyStates[this.socket?.readyState ?? WebSocket.CLOSED] || "closed",
|
||||||
online: navigator.onLine,
|
|
||||||
visibility: document.visibilityState,
|
visibility: document.visibilityState,
|
||||||
uptime_ms: this.authenticatedAt ? Math.max(0, Date.now() - this.authenticatedAt) : 0,
|
uptime_ms: this.authenticatedAt ? Math.max(0, Date.now() - this.authenticatedAt) : 0,
|
||||||
last_connection_uptime_ms: this.lastConnectionUptimeMs,
|
last_connection_uptime_ms: this.lastConnectionUptimeMs,
|
||||||
reconnect_attempt: this.reconnectAttempt,
|
reconnect_attempt: this.reconnectAttempt,
|
||||||
total_reconnects: this.totalReconnects,
|
total_reconnects: this.totalReconnects,
|
||||||
connected_at: this.connectedAt,
|
|
||||||
authenticated_at: this.authenticatedAt,
|
authenticated_at: this.authenticatedAt,
|
||||||
last_message_at: this.lastMessageAt,
|
last_message_at: this.lastMessageAt,
|
||||||
last_close: this.lastClose,
|
last_close: this.lastClose,
|
||||||
buffered_amount: this.socket?.bufferedAmount || 0,
|
buffered_amount: this.socket?.bufferedAmount || 0,
|
||||||
bytes_sent: this.bytesSent,
|
bytes_sent: this.bytesSent,
|
||||||
bytes_received: this.bytesReceived,
|
bytes_received: this.bytesReceived,
|
||||||
network: {
|
|
||||||
effective_type: connection?.effectiveType || null,
|
|
||||||
downlink_mbps: Number.isFinite(connection?.downlink) ? connection.downlink : null,
|
|
||||||
rtt_ms: Number.isFinite(connection?.rtt) ? connection.rtt : null,
|
|
||||||
save_data: typeof connection?.saveData === "boolean" ? connection.saveData : null,
|
|
||||||
},
|
|
||||||
latency: this.latencyStats(),
|
latency: this.latencyStats(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<section id="auth-panel" class="auth-panel" hidden>
|
<section id="auth-panel" class="auth-panel" hidden>
|
||||||
<h3 id="auth-mode-title">Log in</h3>
|
<h3 id="auth-mode-title">Log in</h3>
|
||||||
<label id="auth-email-field">E-mail / organization login
|
<label id="auth-email-field"><span id="auth-email-label">E-mail</span>
|
||||||
<input id="auth-email" name="username" type="email" maxlength="320" autocomplete="username"
|
<input id="auth-email" name="username" type="email" maxlength="320" autocomplete="username"
|
||||||
placeholder="you@example.com">
|
placeholder="you@example.com">
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user