mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-11 07:52:50 -05:00
rename backend to core
This commit is contained in:
552
core/internal/server/network/API.md
Normal file
552
core/internal/server/network/API.md
Normal file
@@ -0,0 +1,552 @@
|
||||
# NetworkManager API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The network manager API provides methods for managing WiFi connections, monitoring network state, and handling credential prompts through NetworkManager. 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:**
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```json
|
||||
{
|
||||
"method": "network.credentials.cancel",
|
||||
"params": {
|
||||
"token": "correlation-token"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event Subscriptions
|
||||
|
||||
### Subscribing to Events
|
||||
|
||||
Subscribe to receive network state updates and credential prompts:
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
function cleanup() {
|
||||
clearTimeout(timer);
|
||||
targetSSID = null;
|
||||
closeDialogs();
|
||||
}
|
||||
```
|
||||
|
||||
### Subscribe to Both Services
|
||||
|
||||
Missing `network.credentials` means prompts won't arrive:
|
||||
|
||||
```javascript
|
||||
// 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:
|
||||
```bash
|
||||
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
|
||||
```go
|
||||
type PromptRequest struct {
|
||||
SSID string `json:"ssid"`
|
||||
SettingName string `json:"setting"`
|
||||
Fields []string `json:"fields"`
|
||||
Hints []string `json:"hints"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
```
|
||||
|
||||
### PromptReply
|
||||
```go
|
||||
type PromptReply struct {
|
||||
Secrets map[string]string `json:"secrets"`
|
||||
Save bool `json:"save"`
|
||||
Cancel bool `json:"cancel"`
|
||||
}
|
||||
```
|
||||
|
||||
### NetworkState
|
||||
```go
|
||||
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"`
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user