Skip to content

09 Desarrollo

glitchboi edited this page Oct 30, 2026 · 3 revisions

Desarrollo

Guía para contribuir código a Blackwire. Para entender cómo encajan las piezas antes de tocar nada, lee primero 08 — Arquitectura. El proyecto fue refactorizado de monolito a modular: el backend es una app factory con un router por dominio y la UI es una app de escritorio nativa PySide6/Qt (un shell main.py con un widget por herramienta bajo widgets/). Respeta esa estructura.

Configurar el entorno

Prerrequisitos

  • Python 3.9+
  • PySide6 + Qt — necesarios para ejecutar la app de escritorio (se instalan vía pip/requirements.txt).
  • Git
  • Un editor de código (VS Code recomendado).

Clonar e instalar

git clone https://github.com/Glitchboi-sudo/Blackwire.git
cd Blackwire
make install     # crea el venv, instala dependencias y genera el certificado de mitmproxy

Modo desarrollo

make serve       # arranca el backend en primer plano (Ctrl-C para detener)

make serve corre uvicorn desde backend/ en primer plano; no transpila ningún frontend (la UI es una app nativa). Para trabajar en la interfaz, lanza la app de escritorio en otra terminal con python desktop/run.py (y python desktop/run.py --selftest para un smoke test headless).

No uses python backend/main.py. Los imports asumen que el directorio de trabajo es backend/; arrancar así rompe la resolución de módulos. Usa siempre make serve (o make start).

Comandos del Makefile

Comando Qué hace
make install venv + dependencias + certificado de mitmproxy.
make serve Backend en primer plano.
make start Backend en segundo plano.
make stop Detiene el backend.
make restart Reinicia el backend (necesario tras cambios de backend o de extensiones).
make run Instala, arranca el backend y lanza la app de escritorio.
make verify Verifica dependencias.
make clean Borra el venv, cachés y artefactos de runtime.
make help Lista todos los objetivos.
python desktop/run.py Lanza la app de escritorio (GUI).
python desktop/run.py --selftest Smoke test headless de la app de escritorio.

Importante: no hay hot-reload del backend. FastAPI descubre las extensiones en el arranque, no en runtime. Tras cualquier cambio en backend/ o en una extensión: make restart.


Estructura del proyecto

Blackwire/
├── backend/
│   ├── main.py              # app factory FastAPI (registra routers)
│   ├── config.py            # paths, constantes, validadores
│   ├── schemas.py           # modelos Pydantic + catálogo de ops Chepy
│   ├── db.py                # acceso SQLite por proyecto
│   ├── mitm_addon.py        # addon de mitmproxy (el proxy)
│   ├── chepy_compat.py      # motor Cipher
│   ├── routes/              # 1 router FastAPI por dominio
│   ├── services/            # state.py + proxy_control.py
│   ├── utils/               # httpql, scope, git_manager, extensions_loader, ...
│   └── extensions/          # extensiones (.py)
├── desktop/                 # app de escritorio nativa PySide6/Qt (la única UI)
│   ├── run.py               # lanzador
│   └── blackwire_desktop/
│       ├── api.py           # cliente REST (única frontera HTTP)
│       ├── ws_client.py     # cliente WebSocket (/ws)
│       ├── httpql.py
│       ├── sitemap.py
│       ├── diff.py
│       ├── intruder.py
│       ├── sensitive.py
│       ├── jwt_tool.py
│       ├── rawhttp.py
│       ├── themes.py        # 17 temas + QSS + densidad
│       ├── settings.py      # QSettings
│       ├── commands.py      # paleta de comandos (Ctrl+K)
│       ├── docker_runtime.py # gestiona el contenedor del backend
│       ├── backend_server.py # uvicorn en dev
│       ├── widgets/         # 1 widget por herramienta
│       └── main.py          # shell
├── mcp_server/              # blackwire-mcp (stdio)
├── assets/                  # banner, icono, blackwire.desktop
├── packaging/              # build de escritorio (PyInstaller + Docker)
├── projects/              # 1 DB SQLite por proyecto (blackwire.db + config.json; gitignored)
├── .github/workflows/desktop.yml
├── Makefile
├── pyproject.toml
├── requirements.txt
├── package.json
├── README.md
└── CLAUDE.md

Estrategia de expansión

Antes de tocar main.py, evalúa en este orden — casi todo cabe en un módulo nuevo sin engordar los archivos núcleo:

  1. Lógica de proxy/HTTP → una extensión en backend/extensions/.
  2. Nuevos endpoints de API → backend/routes/{feature}.py, registrado en main.py.
  3. Nuevo widget de UI → desktop/blackwire_desktop/widgets/{feature}.py, cableado en main.py.
  4. Helper compartido → backend/utils/{util}.py o un módulo en desktop/blackwire_desktop/.
  5. Sin alternativa → edita main.py con el diff mínimo posible.

Desarrollo backend

Añadir un endpoint de API

No añadas @app.get(...) en main.py. El patrón correcto es crear (o ampliar) un router de dominio y registrarlo.

Paso 1 — crea/edita backend/routes/mi_feature.py:

from fastapi import APIRouter
from pydantic import BaseModel

router = APIRouter()


class CustomRequest(BaseModel):
    field1: str
    field2: int


@router.get("/api/custom")
async def custom_endpoint():
    """Documentación del endpoint."""
    return {"status": "ok", "data": []}


@router.post("/api/custom")
async def create_custom(data: CustomRequest):
    # accede vía data.field1, data.field2
    return {"status": "ok"}

Paso 2 — regístralo en backend/main.py:

from routes.mi_feature import router as mi_feature_router
# ...
for _router in (..., mi_feature_router):
    app.include_router(_router)

Crear un router en backend/routes/ sin registrarlo en main.py es un error silencioso: FastAPI no lo descubre solo.

Si tu router necesita acceso a la DB u otras dependencias, sigue el patrón de inyección que usan los routers existentes (init_*_routes(get_db=...)), llamado en main.py antes del registro.

Acceder a la base de datos

El acceso a SQLite por proyecto está centralizado en backend/db.py:

from db import get_db

async def query_db():
    db = await get_db()              # conexión a la DB del proyecto activo
    async with db:
        async with db.execute("SELECT * FROM requests") as cursor:
            return await cursor.fetchall()

Usa get_db_with_regex() si necesitas el operador regex de HTTPQL.

Añadir un hook de extensión / lógica de proxy

La lógica que toca el tráfico va en backend/mitm_addon.py (el addon de mitmproxy), nunca en main.py:

class BlackwireAddon:
    def request(self, flow: http.HTTPFlow):
        # ... lógica propia
        self._apply_extensions("on_request", flow)   # delega a las extensiones

Desarrollo de la app de escritorio

Añadir una herramienta / widget

Una herramienta nueva es un QWidget en desktop/blackwire_desktop/widgets/{feature}.py, cableado en main.py. No metas la lógica de la herramienta dentro de main.py.

El widget solo habla con el backend a través de api.py (cliente REST, única frontera HTTP) y escucha eventos en vivo mediante ws_client.py (WebSocket /ws). No hagas llamadas HTTP crudas desde el widget ni mantengas tu propia conexión.

Threading

Nunca bloquees el hilo de la UI. Toda operación de red o cómputo largo va a un QThreadPool (worker en segundo plano) y los resultados se devuelven al hilo de la UI vía señales/slots. Un api.py llamado de forma síncrona en el hilo principal congela la GUI.

Theming

El theming es dirigido por tokens vía QSS, generado en themes.py (17 temas, conmutables en caliente, más densidad: compacta/cómoda/amplia). Para estilar tus widgets usa object names y roles (p. ej. setObjectName(...)) que el QSS global resuelve; no uses estilos inline codificados a mano, porque rompen el cambio de tema en caliente.


Crear una extensión

Resumen — la guía completa con el esquema de UI y la referencia de hooks está en 07 — Sistema de Extensiones.

Paso 1 — backend/extensions/my_extension.py:

EXTENSION_META = {
    "name": "my_extension",
    "title": "My Extension",
    "description": "Lo que hace",
    "ui_schema": {
        "type": "schema-driven",
        "fields": [
            {"name": "setting1", "label": "Setting 1", "type": "text", "default": ""}
        ]
    },
    "default_config": {"enabled": False, "setting1": ""}
}

from mitmproxy import http


class MyExtension:
    name = "my_extension"

    def on_request(self, flow: http.HTTPFlow, cfg: dict, full_config: dict):
        if not cfg.get("enabled"):
            return
        if cfg.get("setting1"):
            flow.request.headers["X-Custom"] = cfg["setting1"]


def register():
    return MyExtension()

Paso 2 — reinicia para que se descubra:

make restart

Paso 3 — pruébala: habilita "My Extension" en la app de escritorio → configura → envía un request de prueba → verifica la modificación.


Pruebas

Pruebas manuales

El estándar actual es la verificación manual contra una sesión de proxy en marcha:

  1. Inicia Blackwire (make serve).
  2. Crea un proyecto de prueba.
  3. Configura el proxy en tu navegador/cliente.
  4. Envía requests y verifica el comportamiento.

Para arreglos de bugs, reproduce primero el fallo y deja constancia del paso de reproducción antes de corregir.

Pruebas automatizadas (pendiente)

Aún no hay suite automatizada. Contribuciones bienvenidas:

  • Tests unitarios de la API del backend.
  • Tests de integración del proxy.
  • Tests E2E del frontend.

Guías para contribuir

Estilo de código

Python:

  • PEP 8: snake_case para funciones/variables, PascalCase para clases, UPPER_SNAKE_CASE para constantes.
  • Type hints y docstrings en el código del backend (las extensiones pueden omitir type hints estrictos por compatibilidad con mitmproxy).
  • Límite de línea: 88 caracteres (convención Black/Ruff).

UI / PySide6:

  • PEP 8, igual que el backend.
  • Cada herramienta es una subclase de QWidget.
  • Theming vía QSS dirigido por tokens (themes.py); nada de estilos inline codificados a mano.

Mensajes de commit (Conventional Commits)

feat: añade analizador JWT
fix: corrige refresh del token de webhook
ext: rate limiter
refactor: divide el router de colecciones
docs: actualiza la wiki de arquitectura

No hagas commit directo a main. Crea siempre una rama: feat/nombre o fix/nombre.

Proceso de Pull Request

  1. Haz fork del repositorio.
  2. Crea una rama: git checkout -b feat/mi-feature.
  3. Implementa y commitea.
  4. Haz push a tu fork.
  5. Abre el PR con: descripción clara, capturas (si hay cambios de UI) y pasos de prueba.

Build de escritorio

El empaquetado de escritorio (PyInstaller + Docker) vive en el directorio packaging/ del repositorio y lo construye el workflow .github/workflows/desktop.yml (disparado por un tag v*). El backend corre en un contenedor Docker aislado gestionado por la propia GUI. (El antiguo repositorio separado Blackwire-compile quedó obsoleto.)

Clone this wiki locally