feat: restructure settings into identity, server, backup and advanced tabs
Drops the per-server pin list down to the one server your account is bound to, adds an enforced network timeout, and replaces the global status toast inside settings with inline feedback and a reusable confirm dialog for rotate/log out. Mail-fetching preferences move into the server tab; the now-superseded claim/recall forms are gone since onboarding covers both.
This commit is contained in:
parent
fb0ca5c8cc
commit
84397a3fdd
6 changed files with 270 additions and 334 deletions
|
|
@ -279,11 +279,12 @@ export function walkChain(username, pinned, current, chain) {
|
|||
|
||||
// Reassembles a byte stream (WebSocket or TCP) into exact-length reads.
|
||||
class ByteStream {
|
||||
constructor(stream) {
|
||||
constructor(stream, timeoutMs = 0) {
|
||||
this.chunks = [];
|
||||
this.length = 0;
|
||||
this.closed = null;
|
||||
this.waiters = [];
|
||||
this.timeoutMs = timeoutMs;
|
||||
stream.onData = (data) => { this.chunks.push(data); this.length += data.length; this.wake(); };
|
||||
stream.onClose = () => this.fail(new SmolError("server closed the connection"));
|
||||
}
|
||||
|
|
@ -305,7 +306,18 @@ class ByteStream {
|
|||
async readExact(n) {
|
||||
if (this.closed) throw this.closed;
|
||||
if (this.length < n) {
|
||||
await new Promise((resolve, reject) => this.waiters.push({ need: n, resolve, reject }));
|
||||
await new Promise((resolve, reject) => {
|
||||
const waiter = { need: n, resolve, reject };
|
||||
this.waiters.push(waiter);
|
||||
if (!this.timeoutMs) return;
|
||||
// Settings' timeout, applied per read: a stalled handshake or request
|
||||
// otherwise waits here forever, since nothing else ever rejects it.
|
||||
setTimeout(() => {
|
||||
if (!this.waiters.includes(waiter)) return; // already settled elsewhere
|
||||
this.waiters = this.waiters.filter(w => w !== waiter);
|
||||
reject(new SmolError(`no response from the server within ${this.timeoutMs / 1000}s`));
|
||||
}, this.timeoutMs);
|
||||
});
|
||||
if (this.closed) throw this.closed;
|
||||
}
|
||||
const out = new Uint8Array(n);
|
||||
|
|
@ -369,8 +381,8 @@ export class Session {
|
|||
|
||||
// Handshake plus §4 pinning. Returns the session, the server's static key as
|
||||
// revealed by the handshake, and whether that key was already pinned.
|
||||
export async function openSession(stream, host, pinned = null) {
|
||||
const wire = new ByteStream(stream);
|
||||
export async function openSession(stream, host, pinned = null, timeoutMs = 0) {
|
||||
const wire = new ByteStream(stream, timeoutMs);
|
||||
const nx = new NxInitiator();
|
||||
const m1 = nx.writeMessage1();
|
||||
stream.send(concat(u16BE(m1.length), m1));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue