Public Access
Give DodoSSH a phone, and a shared shell for both heads to drive
The Android head from docs/android-port.md, taken as far as its step 6. Step 3, the spike, is answered and its throwaway screen is gone: libsodium.so and libe_sqlite3.so are both in the arm64 APK, so NSec resolves its native half on Android despite shipping no Android build, and the local cache opens. Two findings the audit could not have had: Avalonia.Controls.WebView only ships net10.0-android36.0, which settles the open "which Android versions" question at targetSdk 36; and Android has blocked cleartext HTTP since API 28, so the terminal renderer needs a network security config scoped to 127.0.0.1 or the WebView loads nothing. DodoSSH.Client.Shell is new and is why the phone can exist: the view models, the terminal renderer files and the palette moved there so both heads drive one state machine and draw from one set of tokens. The desktop head is otherwise untouched and its 144 tests still pass. The platform pieces behind interfaces that already existed: the profile directory from filesDir, a device key wrapped by a StrongBox-backed key that a fingerprint releases, and a foreground service so a shell outliving a vault lock stays true on a platform that stops backgrounded processes. Sign-in is deliberately absent rather than approximated. It needs an app link, because reusing the desktop loopback listener is the attack RFC 8252 section 8.3 names.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace DodoSSH.Client.Shell.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// One open terminal, as a tab.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A tab is a session id and two strings. It holds no terminal and owns nothing: the pane, its scrollback
|
||||
/// and the shell behind it all live in the renderer and in <c>TerminalWorkspace</c>, and selecting a tab is
|
||||
/// one frame telling the page which pane to show. That is what makes tabs cheap here — the expensive object
|
||||
/// is the WebView, and there is one of those however many tabs are open.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Tabs belong to the shell, not to the vault.</b> Locking disposes the vault and every key it held, and
|
||||
/// deliberately leaves shells running — so a tab list rebuilt per unlock would lose track of sessions that
|
||||
/// are still connected, and the unlock screen's count of them would be the only place they appeared. The
|
||||
/// shell outlives every lock, and so does this.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="sessionId">Identifies this terminal to the renderer.</param>
|
||||
/// <param name="label">The host's name, as the vault has it.</param>
|
||||
/// <param name="address">Who this is logged in as, and where.</param>
|
||||
internal sealed partial class TerminalTabViewModel(uint sessionId, string label, string address)
|
||||
: ObservableObject
|
||||
{
|
||||
internal uint SessionId { get; } = sessionId;
|
||||
|
||||
internal string Label { get; } = label;
|
||||
|
||||
/// <summary>The account and endpoint, for the pane header and the status bar.</summary>
|
||||
internal string Address { get; } = address;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the shell behind this tab is still running.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Cleared when the workspace says the session ended, never inferred from the tab being closed — closing
|
||||
/// a tab removes it, and a removed tab has nothing left to report. A dead tab is kept on purpose: its
|
||||
/// pane still holds the scrollback, and the last thing the remote said is usually why the shell ended.
|
||||
/// </remarks>
|
||||
[ObservableProperty]
|
||||
private bool isLive = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is the tab whose pane is showing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A flag on the tab as well as a selection on the shell, because the strip is an
|
||||
/// <c>ItemsControl</c> of buttons rather than a control that owns a selection — and a button has no
|
||||
/// <c>:selected</c> pseudo-class to style against. The shell writes it; nothing else does.
|
||||
/// </remarks>
|
||||
[ObservableProperty]
|
||||
private bool isSelected;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user