1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-02 11:38:30 -04:00
Files
DankMaterialShell/core/internal/server/network/API.md
T
Ron Harel dc75f1f01d feat(network): initial WiFi hotspot support (#2825)
* Add hotspot contract layer with capability-gated dispatch.

Establish the interface and manager plumbing for hotspot support so backends can opt in without expanding the core Backend contract.
Only backends that implement HotspotBackend get hotspot state propagated; others are forced to unsupported regardless of what they self-report.

* Add IPC handlers and API docs for hotspot actions.

Wire up configure/ start/ stop hotspot through the request router so the QML service layer can drive hotspot operations.
Bump API version to 27 and document the capability-gating contract clients should follow.

* Implement NetworkManager hotspot backend.

Implement HotspotBackend on NetworkManagerBackend with DMS-owned profile management, AP capability detection, and band validation.
Device resolution is deferred to StartHotspot when no device is specified, so profiles survive hardware changes.

* Isolate client Wi-Fi state from AP-mode connections.

Filter AP-mode profiles and access points out of all client Wi-Fi paths so the DMS hotspot (and user-created APs) never appear as saved networks, visible networks, or connected state.
This protects existing client behavior before hotspot controls are exposed in the UI.

* Prefer idle radios for automatic hotspot device selection.

* Add hotspot properties and methods to QML service layer.

Expose hotspot state and actions through DMSNetworkService, NetworkService, and LegacyNetworkService.
Capability is gated on API version and backend-reported support, not backend name checks, and stays stable when Wi-Fi radio is disabled.

* Add hotspot controls to Settings and Control Center.

Settings shows a hotspot setup card as a sibling in the Wi-Fi tab with SSID, password, device, band, save, and start/ stop controls.
Control Center shows a compact row that toggles a configured hotspot or routes to Settings for initial setup.
Both stay visible when Wi-Fi is disabled, explaining the requirement instead of hiding.

* Add translator context to hotspot strings.
2026-07-20 09:29:54 -04:00

18 KiB

NetworkManager API Documentation

Overview

The network manager API provides methods for managing WiFi connections, monitoring network state, and handling credential prompts through NetworkManager or iwd (and systemd-networkd for ethernet only). Communication occurs over a message-based protocol (websocket, IPC, etc.) with event subscriptions for state updates.

API Methods

network.hotspot.configure

Create or update the DMS-managed hotspot profile. Hotspot support is capability-gated: clients should require API v28+, hotspotSupported: true, and hotspotAvailable: true from network state before showing hotspot controls.

For this implementation, only hotspot-capable backends such as NetworkManager should accept this action. Unsupported backends return an error such as hotspot not supported by active network backend.

Configuration changes are rejected while the DMS hotspot is active or activating; stop it before updating the profile.

Request:

{
  "method": "network.hotspot.configure",
  "params": {
    "ssid": "Dank Hotspot",
    "password": "optional-password",
    "device": "wlan0",
    "band": "bg"
  }
}

Parameters:

  • ssid (string, required): Hotspot SSID to advertise.
  • password (string, optional): WPA-PSK password. Omit for an open hotspot when the backend allows it.
  • device (string, optional): Wi-Fi interface name to use, for example wlan0. When omitted, the backend picks an AP-capable radio at start time, preferring one that is already hosting the hotspot, then an idle radio, and only as a last resort a radio carrying an active connection (which NetworkManager will disconnect). Network state exposes apCapable on each wifiDevices entry so clients can predict this choice.
  • band (string, optional): Requested NetworkManager band: bg for 2.4GHz or a for 5GHz.

Response:

{
  "success": true,
  "message": "hotspot configured"
}

network.hotspot.start

Start the previously configured DMS-managed hotspot profile. This action does not accept or require SSID/password parameters; call network.hotspot.configure first when changing hotspot settings.

A successful response only means the activation was requested; the outcome is reported asynchronously through network state updates. While activation is in flight, hotspotActivating is true; on success hotspotEnabled becomes true; on failure hotspotActivating returns to false and hotspotLastError carries one of hotspot-ip-config-failed (IP sharing setup failed, commonly a missing dnsmasq, which NetworkManager's shared IPv4 method requires), hotspot-supplicant-failed (the Wi-Fi driver could not start AP mode), or hotspot-failed. hotspotLastError is cleared on the next successful start.

Request:

{
  "method": "network.hotspot.start"
}

Response:

{
  "success": true,
  "message": "hotspot started"
}

network.hotspot.stop

Stop the active DMS-managed hotspot connection. It must not stop arbitrary user-created hotspot profiles.

Request:

{
  "method": "network.hotspot.stop"
}

Response:

{
  "success": true,
  "message": "hotspot stopped"
}

network.hotspot.getSecrets

Retrieve the stored password of the DMS-managed hotspot profile, for prefilling edit forms. Returns an empty string for an open hotspot. Network state exposes hotspotSecured so clients can tell an open hotspot apart from a secured one without fetching the secret.

Request:

{
  "method": "network.hotspot.getSecrets"
}

Response:

{
  "password": "the-stored-psk"
}

network.wifi.connect

Initiate a WiFi connection.

Request:

{
  "method": "network.wifi.connect",
  "params": {
    "ssid": "NetworkName",
    "password": "optional-password",
    "interactive": true
  }
}

Parameters:

  • ssid (string, required): Network SSID
  • password (string, optional): Pre-shared key for WPA/WPA2/WPA3 networks
  • interactive (boolean, optional): Enable credential prompting if authentication fails or password is missing. Automatically set to true when connecting to secured networks without providing a password.

Response:

{
  "success": true,
  "message": "connecting"
}

Behavior:

  • Returns immediately; connection happens asynchronously
  • State updates delivered via network service subscription
  • Credential prompts delivered via network.credentials service subscription

network.credentials.submit

Submit credentials in response to a prompt.

Request:

{
  "method": "network.credentials.submit",
  "params": {
    "token": "correlation-token",
    "secrets": {
      "psk": "password"
    },
    "save": true
  }
}

Parameters:

  • token (string, required): Token from credential prompt
  • secrets (object, required): Key-value map of credential fields
  • save (boolean, optional): Whether to persist credentials (default: false)

Common secret fields:

  • psk: Pre-shared key for WPA2/WPA3 personal networks
  • identity: Username for 802.1X enterprise networks
  • password: Password for 802.1X enterprise networks

network.credentials.cancel

Cancel a credential prompt.

Request:

{
  "method": "network.credentials.cancel",
  "params": {
    "token": "correlation-token"
  }
}

Event Subscriptions

Subscribing to Events

Subscribe to receive network state updates and credential prompts:

{
  "method": "subscribe",
  "params": {
    "services": ["network", "network.credentials"]
  }
}

Both services are required for full connection handling. Missing network.credentials means credential prompts won't be received.

network Service Events

State updates are sent whenever network configuration changes:

{
  "service": "network",
  "data": {
    "networkStatus": "wifi",
    "isConnecting": false,
    "connectingSSID": "",
    "wifiConnected": true,
    "wifiSSID": "MyNetwork",
    "wifiIP": "192.168.1.100",
    "lastError": ""
  }
}

State fields:

  • networkStatus: Current connection type (wifi, ethernet, disconnected)
  • isConnecting: Whether a connection attempt is in progress
  • connectingSSID: SSID being connected to (empty when idle)
  • wifiConnected: Whether associated with an access point
  • wifiSSID: Currently connected network name
  • wifiIP: Assigned IP address (empty until DHCP completes)
  • savedWifiNetworks (API v26+): Saved WiFi profiles exposed at SSID granularity. If a backend has multiple profiles for the same SSID, DMS merges them into one SSID-level entry. Clients talking to older servers should derive saved visible networks from wifiNetworks entries where saved is true.
  • savedWifiNetworks[].outOfRange (API v26+): Whether the saved profile is not currently visible in scan results. Fallback entries derived from wifiNetworks should be treated as visible (outOfRange: false).
  • hotspotSupported (API v28+): Whether the active backend implements hotspot actions.
  • hotspotAvailable (API v28+): Whether hotspot support is usable on this backend/device set. For NetworkManager this means at least one AP-capable managed Wi-Fi device exists, independent of Wi-Fi radio enabled state.
  • hotspotConfigured (API v28+): Whether the DMS-managed hotspot profile exists.
  • hotspotEnabled (API v28+): Whether the DMS-managed hotspot profile is currently active.
  • hotspotActivating (API v28+): Whether hotspot activation is currently in progress.
  • hotspotSecured (API v28+): Whether the configured hotspot uses password-based security.
  • hotspotSSID (API v28+): Configured DMS hotspot SSID.
  • hotspotDevice (API v28+): Optional configured Wi-Fi device for the DMS hotspot.
  • hotspotBand (API v28+): Optional configured hotspot band (bg or a).
  • hotspotLastError (API v28+): Machine-readable error from the most recent failed hotspot activation. Cleared when the next start succeeds.
  • lastError: Error message from last failed connection attempt

network.credentials Service Events

Credential prompts are sent when authentication is required:

{
  "service": "network.credentials",
  "data": {
    "token": "unique-prompt-id",
    "ssid": "NetworkName",
    "setting": "802-11-wireless-security",
    "fields": ["psk"],
    "hints": ["wpa3", "sae"],
    "reason": "Credentials required"
  }
}

Prompt fields:

  • token: Unique identifier for this prompt (use in submit/cancel)
  • ssid: Network requesting credentials
  • setting: Authentication type (802-11-wireless-security for personal WiFi, 802-1x for enterprise)
  • fields: Array of required credential field names
  • hints: Additional context about the network type
  • reason: Human-readable explanation (e.g., "Previous password was incorrect")

Connection Flow

Typical Timeline

T+0ms     Call network.wifi.connect
T+10ms    Receive {"success": true, "message": "connecting"}
T+100ms   State update: isConnecting=true, connectingSSID="Network"
T+500ms   Credential prompt (if needed)
T+1000ms  Submit credentials
T+3000ms  State update: wifiConnected=true, wifiIP="192.168.x.x"

State Machine

IDLE
  |
  | network.wifi.connect
  v
CONNECTING (isConnecting=true, connectingSSID set)
  |
  +-- Needs credentials
  |     |
  |     v
  |   PROMPTING (credential prompt event)
  |     |
  |     | network.credentials.submit
  |     v
  |   back to CONNECTING
  |
  +-- Success
  |     |
  |     v
  |   CONNECTED (wifiConnected=true, wifiIP set, isConnecting=false)
  |
  +-- Failure
        |
        v
      ERROR (isConnecting=false, !wifiConnected, lastError set)

Connection Success Detection

A connection is successful when all of the following are true:

  1. wifiConnected is true
  2. wifiIP is set and non-empty
  3. wifiSSID matches the target network
  4. isConnecting is false

Do not rely on wifiConnected alone - the device may be associated with an access point but not have an IP address yet.

Example:

function isConnectionComplete(state, targetSSID) {
    return state.wifiConnected &&
           state.wifiIP &&
           state.wifiIP !== "" &&
           state.wifiSSID === targetSSID &&
           !state.isConnecting;
}

Error Handling

Error Detection

Errors occur when a connection attempt stops without success:

function checkForFailure(state, wasConnecting, targetSSID) {
    // Was connecting, now idle, but not connected
    if (wasConnecting &&
        !state.isConnecting &&
        state.connectingSSID === "" &&
        !state.wifiConnected) {
        return state.lastError || "Connection failed";
    }
    return null;
}

Common Error Scenarios

Wrong Password

Detection methods:

  1. Quick failure (< 3 seconds from start)
  2. lastError contains "password", "auth", or "secrets"
  3. Second credential prompt with reason: "Previous password was incorrect"

Handling:

if (prompt.reason === "Previous password was incorrect") {
    // Show error, clear password field, re-focus input
}

Network Out of Range

Detection:

  • lastError contains "not-found" or "connection-attempt-failed"

Connection Timeout

Detection:

  • isConnecting remains true for > 30 seconds

Implementation:

let timeout = setTimeout(() => {
    if (currentState.isConnecting) {
        handleTimeout();
    }
}, 30000);

DHCP Failure

Detection:

  • wifiConnected is true
  • wifiIP is empty after 15+ seconds

Error Message Translation

Map technical errors to user-friendly messages:

lastError value Meaning User message
secrets-required Password needed "Please enter password"
authentication-failed Wrong password "Incorrect password"
connection-removed Profile deleted "Network configuration removed"
connection-attempt-failed Generic failure "Failed to connect"
network-not-found Out of range "Network not found"
(timeout) Timeout "Connection timed out"

Credential Handling

Secret Agent Architecture

The credential system uses a broker pattern:

NetworkManager -> SecretAgent -> PromptBroker -> UI -> User
                                       ^
                                       |
                                  User Response
                                       |
NetworkManager <- SecretAgent <- PromptBroker <- UI

Implementing a Broker

type CustomBroker struct {
    ui       UIInterface
    pending  map[string]chan network.PromptReply
}

func (b *CustomBroker) Ask(ctx context.Context, req network.PromptRequest) (string, error) {
    token := generateToken()
    b.pending[token] = make(chan network.PromptReply, 1)

    // Send to UI
    b.ui.ShowCredentialPrompt(token, req)

    return token, nil
}

func (b *CustomBroker) Wait(ctx context.Context, token string) (network.PromptReply, error) {
    select {
    case <-ctx.Done():
        return network.PromptReply{}, errors.New("timeout")
    case reply := <-b.pending[token]:
        return reply, nil
    }
}

func (b *CustomBroker) Resolve(token string, reply network.PromptReply) error {
    if ch, ok := b.pending[token]; ok {
        ch <- reply
        close(ch)
        delete(b.pending, token)
    }
    return nil
}

Credential Field Types

Personal WiFi (802-11-wireless-security):

  • Fields: ["psk"]
  • UI: Single password input

Enterprise WiFi (802-1x):

  • Fields: ["identity", "password"]
  • UI: Username and password inputs

Building Secrets Object

function buildSecrets(setting, fields, formData) {
    let secrets = {};

    if (setting === "802-11-wireless-security") {
        secrets.psk = formData.password;
    } else if (setting === "802-1x") {
        secrets.identity = formData.username;
        secrets.password = formData.password;
    }

    return secrets;
}

Best Practices

Track Target Network

Always store which network you're connecting to:

let targetSSID = null;

function connect(ssid) {
    targetSSID = ssid;
    // send request
}

function onStateUpdate(state) {
    if (!targetSSID) return;

    if (state.wifiSSID === targetSSID && state.wifiConnected && state.wifiIP) {
        // Success for the network we care about
        targetSSID = null;
    }
}

Implement Timeouts

Never wait indefinitely for a connection:

const CONNECTION_TIMEOUT = 30000; // 30 seconds
const DHCP_TIMEOUT = 15000;       // 15 seconds

let timer = setTimeout(() => {
    if (stillConnecting) {
        handleTimeout();
    }
}, CONNECTION_TIMEOUT);

Handle Credential Re-prompts

Wrong passwords trigger a second prompt:

function onCredentialPrompt(prompt) {
    if (prompt.reason.includes("incorrect")) {
        // Show error, but keep dialog open
        showError("Wrong password");
        clearPasswordField();
    } else {
        // First time prompt
        showDialog(prompt);
    }
}

Clean Up State

Reset tracking variables on success, failure, or cancellation:

function cleanup() {
    clearTimeout(timer);
    targetSSID = null;
    closeDialogs();
}

Subscribe to Both Services

Missing network.credentials means prompts won't arrive:

// Correct
services: ["network", "network.credentials"]

// Wrong - will miss credential prompts
services: ["network"]

Testing

Connection Test Checklist

  • Connect to open network
  • Connect to WPA2 network with password provided
  • Connect to WPA2 network without password (triggers prompt)
  • Enter wrong password (verify error and re-prompt)
  • Cancel credential prompt
  • Connection timeout after 30 seconds
  • DHCP timeout detection
  • Network out of range
  • Reconnect to already-configured network

Verifying Secret Agent Setup

Check connection profile flags:

nmcli connection show "NetworkName" | grep flags
# Should show: 802-11-wireless-security.psk-flags: 1 (agent-owned)

Check agent registration in logs:

INFO: Registered with NetworkManager as secret agent

Security

  • Never log credential values (passwords, PSKs)
  • Clear password fields when dialogs close
  • Implement prompt timeouts (default: 2 minutes)
  • Validate user input before submission
  • Use secure channels for credential transmission

Troubleshooting

Credential prompt doesn't appear

Check:

  • Subscribed to both network and network.credentials
  • Connection has interactive: true
  • Secret flags set to AGENT_OWNED (value: 1)
  • Broker registered successfully

Connection succeeds without prompting

Cause: NetworkManager found saved credentials

Solution: Delete existing connection first, or use different credentials

State updates seem delayed

Expected behavior: State changes occur in rapid succession during connection

Solution: Debounce UI updates; only act on final state

Multiple rapid credential prompts

Cause: Connection profile has incorrect flags or conflicting agents

Solution:

  • Check only one agent is running
  • Verify psk-flags value
  • Check NetworkManager logs for agent conflicts

Data Structures Reference

PromptRequest

type PromptRequest struct {
    SSID        string   `json:"ssid"`
    SettingName string   `json:"setting"`
    Fields      []string `json:"fields"`
    Hints       []string `json:"hints"`
    Reason      string   `json:"reason"`
}

PromptReply

type PromptReply struct {
    Secrets map[string]string `json:"secrets"`
    Save    bool              `json:"save"`
    Cancel  bool              `json:"cancel"`
}

NetworkState

type NetworkState struct {
    NetworkStatus  string `json:"networkStatus"`
    IsConnecting   bool   `json:"isConnecting"`
    ConnectingSSID string `json:"connectingSSID"`
    WifiConnected  bool   `json:"wifiConnected"`
    WifiSSID       string `json:"wifiSSID"`
    WifiIP         string `json:"wifiIP"`
    LastError      string `json:"lastError"`
}