From 815adb5cfad0d3530dbd93ec2e27e54800a00139 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Mon, 29 Dec 2025 08:09:35 +0100 Subject: [PATCH] feat(bridge): Add pending contacts management API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added two new HTTP endpoints to meshcore-bridge for managing pending contacts (contacts awaiting manual approval when manual_add_contacts mode is enabled): New endpoints: - GET /pending_contacts - List all pending contacts awaiting approval - Parses meshcli output format: "Name: " - Returns JSON array with {name, public_key} objects - Includes raw_stdout for debugging - POST /add_pending - Approve and add a pending contact - Accepts JSON body: {"selector": ""} - Validates selector is non-empty string - Executes meshcli add_pending command via persistent session Additional changes: - Added curl to mc-webui Dockerfile for testing endpoints - Updated README with Testing Bridge API section - Included example curl commands and expected responses Implementation notes: - Uses existing MeshCLISession.execute_command() - no new processes - Same persistent session and command queue architecture - Consistent error handling with existing /cli endpoint Enables future UI for manual contact approval workflow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- Dockerfile | 3 + README.md | 46 +++++++++++++ meshcore-bridge/bridge.py | 133 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+) diff --git a/Dockerfile b/Dockerfile index c8b8ef7..9e3c7d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,9 @@ FROM python:3.11-slim +# Install curl for testing +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + # Set working directory WORKDIR /app diff --git a/README.md b/README.md index 6695181..a84bd2c 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,52 @@ docker compose up -d --build docker compose ps ``` +## Testing Bridge API + +The `meshcore-bridge` container exposes HTTP endpoints for pending contact management. + +### Test Pending Contacts Endpoints + +```bash +# List pending contacts (from inside mc-webui container or server) +curl -s http://meshcore-bridge:5001/pending_contacts | jq + +# Add a pending contact +curl -s -X POST http://meshcore-bridge:5001/add_pending \ + -H 'Content-Type: application/json' \ + -d '{"selector":"Skyllancer"}' | jq + +# Example response for GET /pending_contacts: +# { +# "success": true, +# "pending": [ +# { +# "name": "Skyllancer", +# "public_key": "f9ef..." +# }, +# { +# "name": "KRA Reksio mob2🐕", +# "public_key": "41d5..." +# } +# ], +# "raw_stdout": "Skyllancer: f9ef...\nKRA Reksio mob2🐕: 41d5..." +# } + +# Example response for POST /add_pending: +# { +# "success": true, +# "stdout": "Contact added successfully", +# "stderr": "", +# "returncode": 0 +# } +``` + +**Note:** These endpoints require `manual_add_contacts` mode to be enabled in meshcli: +```bash +# Enable manual contact approval (run in meshcli interactive mode) +set manual_add_contacts on +``` + ## Troubleshooting ### Device not found diff --git a/meshcore-bridge/bridge.py b/meshcore-bridge/bridge.py index 1645a0f..4ac08ae 100644 --- a/meshcore-bridge/bridge.py +++ b/meshcore-bridge/bridge.py @@ -496,6 +496,139 @@ def execute_cli(): }), 500 +@app.route('/pending_contacts', methods=['GET']) +def get_pending_contacts(): + """ + Get list of pending contacts awaiting approval. + + Response JSON: + { + "success": true, + "pending": [ + {"name": "Skyllancer", "public_key": "f9ef..."}, + {"name": "KRA Reksio mob2🐕", "public_key": "41d5..."} + ], + "raw_stdout": "..." + } + """ + try: + # Check session health + if not meshcli_session or not meshcli_session.process: + return jsonify({ + 'success': False, + 'error': 'meshcli session not initialized', + 'pending': [] + }), 503 + + # Execute pending_contacts command + result = meshcli_session.execute_command(['pending_contacts'], timeout=DEFAULT_TIMEOUT) + + if not result['success']: + return jsonify({ + 'success': False, + 'error': result.get('stderr', 'Command failed'), + 'pending': [], + 'raw_stdout': result.get('stdout', '') + }), 200 + + # Parse stdout + stdout = result.get('stdout', '').strip() + pending = [] + + if stdout: + for line in stdout.split('\n'): + line = line.strip() + # Parse lines with format: "Name: " + if ':' in line: + parts = line.split(':', 1) + if len(parts) == 2: + name = parts[0].strip() + public_key = parts[1].strip().replace(' ', '') # Remove spaces from hex + if name and public_key: + pending.append({ + 'name': name, + 'public_key': public_key + }) + + return jsonify({ + 'success': True, + 'pending': pending, + 'raw_stdout': stdout + }), 200 + + except Exception as e: + logger.error(f"API error in /pending_contacts: {e}") + return jsonify({ + 'success': False, + 'error': str(e), + 'pending': [] + }), 500 + + +@app.route('/add_pending', methods=['POST']) +def add_pending_contact(): + """ + Add a pending contact by name or public key. + + Request JSON: + { + "selector": "" + } + + Response JSON: + { + "success": true, + "stdout": "...", + "stderr": "", + "returncode": 0 + } + """ + try: + data = request.get_json() + + if not data or 'selector' not in data: + return jsonify({ + 'success': False, + 'stdout': '', + 'stderr': 'Missing required field: selector', + 'returncode': -1 + }), 400 + + selector = data['selector'] + + # Validate selector is non-empty string + if not isinstance(selector, str) or not selector.strip(): + return jsonify({ + 'success': False, + 'stdout': '', + 'stderr': 'selector must be a non-empty string', + 'returncode': -1 + }), 400 + + # Check session health + if not meshcli_session or not meshcli_session.process: + return jsonify({ + 'success': False, + 'stdout': '', + 'stderr': 'meshcli session not initialized', + 'returncode': -1 + }), 503 + + # Execute add_pending command + result = meshcli_session.execute_command(['add_pending', selector.strip()], timeout=DEFAULT_TIMEOUT) + + return jsonify(result), 200 + + except Exception as e: + logger.error(f"API error in /add_pending: {e}") + return jsonify({ + 'success': False, + 'stdout': '', + 'stderr': str(e), + 'returncode': -1 + }), 500 + + if __name__ == '__main__': logger.info(f"Starting MeshCore Bridge on port 5001") logger.info(f"Serial port: {MC_SERIAL_PORT}")