/* What the phone says when things go wrong. The backend answers in Rust. `ClientError`'s Display impls (crates/ soulseek-client/src/lib.rs) hand up `io: {0}`, `protocol: {0}`, `login failed: INVALIDPASS`, `user {0} is offline or unknown`, `timed out`; the commands add their own literals (`not connected to Soulseek`, `no stored credentials`, `no cache dir`, `could not reach server: {e}`); rusqlite and std::io come through `e.to_string()`. Fifty-nine places in src/mobile/ put one of those straight into a field a person reads, and the red toast drew it verbatim — a phone in a lift showed, under the password box, "could not reach server: io: failed to lookup address information: no address associated with hostname". ONE translator, called where the words are DRAWN rather than where they are caught. The stores keep the raw string — Diagnostics and the walk still see exactly what Rust said — and only the sentence on screen is ours, so every future `String(e)` is covered by the same rule as the fifty-nine already here. A message the backend already wrote in English ("The playlist needs a name") matches nothing and passes through untouched. The order below is the order they are tried: the most specific shape first, the two catch-alls (`io:` / `protocol:`) last but one, and anything unrecognised kept as it is, capped. */ /** The message inside whatever was thrown: an Error, a plain string (which * is what Tauri rejects with), or something with neither. */ const clean = (e) => String(e?.message ?? e ?? "").replace(/^Error:\s*/, "").trim(); const RULES = [ /* this round's own guard (src-tauri/src/pipeline.rs): the library root could not be made or read, so the rescan refused rather than pruning every row whose file it could not see */ [/^library-folder-missing:\s*(.*)$/i, (m) => `The library folder isn't there any more — ${m[1]}. Reconnect the storage, or pick the folder again in You.`], /* with_client()'s funnel, and start_search's plainer version */ [/not connected( to (soulseek|server))?$/i, () => "Not signed in to Soulseek. Sign in on Community and try again."], [/^no stored credentials$/i, () => "No Soulseek account saved on this phone yet. Sign in on Community."], /* the server's own refusal words, which are not for reading */ [/^login failed:\s*(.*)$/i, (m) => (/invalidpass/i.test(m[1]) ? "Soulseek refused that password." : /invalidusername/i.test(m[1]) ? "Soulseek refused that username." : `Soulseek refused the sign-in (${m[1]}).`)], [/^could not reach server/i, () => "Couldn't reach the Soulseek server. Check the connection and try again."], [/^timed out$/i, () => "The network didn't answer in time."], [/^user (.+?) is offline or unknown$/i, (m) => `${m[1]} isn't online right now.`], [/^could not reach peer (.+)$/i, (m) => `${m[1]}'s client didn't answer — offline, or behind a firewall.`], [/^transfer rejected:\s*(.*)$/i, (m) => `They refused the transfer${m[1] ? ` (${m[1].replace(/\.$/, "")})` : ""}.`], [/^(io|protocol):\s*(.*)$/i, () => "The network didn't answer. Check the connection and try again."], /* the library's own two: a database that will not open, and a folder the phone has not been given */ [/unable to open database file|os error (2|3)\b|cannot find the (path|file)/i, () => "The library folder isn't readable right now."], [/permission denied|os error 13\b/i, () => "The phone refused access to that folder — check the app's storage permission in You."], [/^no cache dir$/i, () => "This phone has no cache folder to work in."], /* a page with no Tauri under it — the browser walk, and only ever that */ [/reading 'invoke'|transformCallback/i, () => "The app's backend isn't here (this is the browser walk)."], ]; /** Longer than this and it was never a sentence anyone meant to write. */ const CAP = 180; /** The backend's error → something a person can act on. */ export function say(e) { const raw = clean(e); if (!raw) return ""; for (const [re, fn] of RULES) { const m = re.exec(raw); if (m) return fn(m); } return raw.length > CAP ? raw.slice(0, CAP - 1) + "…" : raw; } /** Is this the kind of trouble Community fixes? The two places that offer a * way to sign in rather than a Try again ask here. */ export const signedOut = (e) => /not connected|no stored credentials/i.test(clean(e));