1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 22:15:38 -05:00
Files
DankMaterialShell/core/internal/server/network/API.md

13 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.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)
  • 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"`
}