mirror of
https://gitgud.io/jcmoon/sockchat-rs.git
synced 2026-10-18 17:24:11 -04:00
The rooms are published after all -- I had looked in the wrong place and
concluded they were not.
They are not on the websocket: dumping raw frames shows the server only
ever sends permissions, motd, history, messages, users and user. And
they are not on /chat, which contains just `const room_id = 1` and an
iframe. They are in that iframe, /test-chat, as
<a class="chat-room" data-id="20">Lolcows</a>
chat::rooms fetches and parses that, and the list is now sent to the UI
as Event::Rooms. The switcher shows real names -- General, Lolcows,
Beauty Parlor, Fishtank, Gunt, Keno Kasino, SPORTS!! -- in the page's own
order, which is editorial rather than sorted by ID. The status bar shows
the current room's name too.
Config `rooms` entries still work, now as overrides for a fetched name or
as labels for rooms the page does not advertise. The fetch is
best-effort and runs alongside connecting: if it fails the switcher still
takes a typed number.
tests/frames_probe.rs and tests/rooms_probe.rs record how this was
established, so the next person does not repeat the same dead ends.
Co-Authored-By: Claude Fable 5 <[email protected]>
100 lines
3.5 KiB
Rust
100 lines
3.5 KiB
Rust
//! Dumps raw websocket frames so we can see every key the server sends, not
|
|
//! just the ones the client currently deserializes.
|
|
//!
|
|
//! Uses the saved session; no credentials needed.
|
|
//!
|
|
//! cargo test --test frames_probe -- --ignored --nocapture
|
|
|
|
use std::collections::BTreeSet;
|
|
use std::time::Duration;
|
|
|
|
use futures_util::{SinkExt, StreamExt};
|
|
use sockchat::config::Config;
|
|
use sockchat::net::Transport;
|
|
use sockchat::net::http::{CookieJar, HttpClient};
|
|
use tokio_tungstenite::tungstenite::Message as WsMessage;
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires network and bootstraps Tor"]
|
|
async fn probe_socket_frames() {
|
|
sockchat::net::init_crypto();
|
|
let cfg = Config::load().expect("config");
|
|
let transport = Transport::bootstrap_tor().await.expect("Tor bootstrap");
|
|
|
|
// Clear the gate over HTTP first so the upgrade is not challenged.
|
|
let jar = CookieJar::new();
|
|
jar.seed(&cfg.cookies);
|
|
let _ = sockchat::auth::store::load(&jar);
|
|
let http = HttpClient::new(transport.clone(), jar.clone(), cfg.user_agent.clone());
|
|
match sockchat::net::kiwiflare::clear(&http, &cfg.http_url(), 8).await {
|
|
Ok(n) => eprintln!("cleared gate ({n})"),
|
|
Err(e) => eprintln!("gate: {e:#}"),
|
|
}
|
|
|
|
let ws = sockchat::chat::socket::connect(&cfg, &transport, jar.header())
|
|
.await
|
|
.expect("websocket");
|
|
let (mut sink, mut stream) = ws.split();
|
|
sink.send(WsMessage::text(format!("/join {}", cfg.room)))
|
|
.await
|
|
.expect("join");
|
|
|
|
let mut keys_seen: BTreeSet<String> = BTreeSet::new();
|
|
let deadline = tokio::time::Instant::now() + Duration::from_secs(45);
|
|
|
|
for i in 0..40 {
|
|
let frame = match tokio::time::timeout_at(deadline, stream.next()).await {
|
|
Ok(Some(Ok(f))) => f,
|
|
Ok(Some(Err(e))) => {
|
|
eprintln!("stream error: {e}");
|
|
break;
|
|
}
|
|
Ok(None) => break,
|
|
Err(_) => {
|
|
eprintln!("-- no more frames within the deadline --");
|
|
break;
|
|
}
|
|
};
|
|
|
|
let text = match frame {
|
|
WsMessage::Text(t) => t.to_string(),
|
|
WsMessage::Binary(b) => String::from_utf8_lossy(&b).into_owned(),
|
|
other => {
|
|
eprintln!("[{i}] non-text frame: {other:?}");
|
|
continue;
|
|
}
|
|
};
|
|
|
|
// Record the top-level keys of every JSON object we see.
|
|
if let Ok(serde_json::Value::Object(map)) = serde_json::from_str::<serde_json::Value>(&text)
|
|
{
|
|
for k in map.keys() {
|
|
keys_seen.insert(k.clone());
|
|
}
|
|
eprintln!("[{i}] object keys: {:?}", map.keys().collect::<Vec<_>>());
|
|
// Print anything that is not the bulk message/user payload in full.
|
|
for (k, v) in &map {
|
|
if k == "messages" || k == "users" {
|
|
eprintln!(
|
|
" {k}: <{} entries>",
|
|
v.as_array().map(|a| a.len()).unwrap_or(0)
|
|
);
|
|
continue;
|
|
}
|
|
let rendered = serde_json::to_string(v).unwrap_or_default();
|
|
eprintln!(
|
|
" {k}: {}",
|
|
rendered.chars().take(1200).collect::<String>()
|
|
);
|
|
}
|
|
} else {
|
|
eprintln!(
|
|
"[{i}] plaintext: {}",
|
|
text.chars().take(200).collect::<String>()
|
|
);
|
|
}
|
|
}
|
|
|
|
eprintln!("\n=== all top-level keys observed ===\n{keys_seen:?}");
|
|
}
|