# Electron ↔ Python IPC Reference

This document is the canonical reference for the IPC channel between the
Electron frontend and the Python tracking engine.

---

## Transport Layer

| Platform | Transport | Address |
|----------|-----------|---------|
| macOS / Linux | TCP socket | `127.0.0.1:54321` |
| Windows | Named Pipe | `\\.\pipe\workstatus_python_ipc` |

The correct server is selected automatically by `ipc_server.py`.
`electron_comm.py` is the Python-side facade; use `get_electron_comm()` for
all message sending.

---

## Windows vs Linux/macOS (framing only; same handshake)

**Windows (named pipe, MESSAGE mode)** — keep behavior stable:

- Pipe name: `\\.\pipe\workstatus_python_ipc` (`pipe_client.js` / `pipe_server.py`).
- Each Electron `client.write()` is one complete message on the Python side (no TCP-style streaming).
- Payload is UTF-8 JSON; a trailing `\n` is **not required** for framing (MESSAGE mode defines boundaries).

**Linux / macOS (TCP stream)**

- `127.0.0.1:54321` — each message **must** end with `\n` (newline-delimited JSON).

Handshake **message types and order** are identical on every platform.

---

## Wire Protocol

Payload shape (all platforms):

```
{"type": "<message_type>", "data": {...}}
```

- **TCP (macOS / Linux):** send as **newline-delimited JSON** — one object per line, `\n` terminator (`tcp_server.py` / `tcpClient.js`).
- **Named pipe (Windows):** same JSON object as bytes; typically **one write per message** from Electron; Python reads one message per pipe read (`pipe_server.py`).
- A connection is long-lived for the app session.
- The handshake must complete before application messages are exchanged.

### Handshake sequence

```
Electron  →  Python : {"type": "hello_from_electron"}
Python    →  Electron: {"type": "hello_from_python", "timestamp": <float>}
Python    →  Electron: {"type": "ready", "data": {
                          "status": "ready",
                          "message": "WS_Engine ready (TCP/Pipe connected)",
                          "capabilities": ["set_project_task", "stop"],
                          "version": "<PYTHON_APP_VERSION>"
                        }}
```

On **TCP**, each line above is sent with a trailing `\n`. On **Windows MESSAGE pipe**, each line is one write/read; `\n` is not required for framing.

After handshake, any message in either direction may flow freely.

---

## Messages: Python → Electron

### `ready`
Sent at handshake completion and again whenever Python's internal state
transitions to "ready" (e.g. after re-connection).

```json
{
  "type": "ready",
  "data": {
    "status": "ready",
    "message": "WS_Engine ready",
    "capabilities": ["set_project_task", "stop"],
    "version": "1.2.3"
  }
}
```

---

### `tracking_error`
**Sent whenever a subsystem fails silently** — the primary mechanism for
surfacing "app is running but not tracking" failures to the user.

```json
{
  "type": "tracking_error",
  "data": {
    "code": "[E075]",
    "message": "Screenshot permission denied. Grant Screen Recording in System Settings → Privacy & Security.",
    "recoverable": false,
    "source": "screenshot"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `code` | string | `[Exxx]` tag — see `ERRORS.md` for the full catalogue |
| `message` | string | Human-readable explanation with a remediation hint |
| `recoverable` | bool | `true` = will auto-retry / self-heal; `false` = user action required |
| `source` | string | Subsystem: `"screenshot"`, `"activity"`, `"websocket"`, `"ipc"`, `"network"`, `"engine"` |

**Electron should display this to the user** (toast / notification banner) so
they know tracking is broken even though the window appears normal.

Error codes that trigger `tracking_error`:

| Code | `recoverable` | Trigger |
|------|---------------|---------|
| E070 | false | Screenshot capture returns 0 images |
| E071 | true | S3 upload fails |
| E075 | false | OS permission denied for screen capture |
| E076 | true | Disk full when saving screenshot |
| E080 | false | Keyboard/mouse listener dead after restart failure |
| E105 | false | IPC server failed to start |
| E107 | true | WebSocket disconnected after max retries |

---

### `screenshot_captured`
Sent once per capture cycle (not per individual image) to confirm a screenshot
was taken and saved.

```json
{"type": "screenshot_captured", "data": {}}
```

---

### `monitoring_status`
Sent in response to `get_monitoring_status` or on state change.

```json
{
  "type": "monitoring_status",
  "data": {
    "is_running": true,
    "project_id": 42,
    "task_id": 7,
    "user_id": 123
  }
}
```

---

### `stop_completed`
Sent after Python has finished flushing data on a stop command.

```json
{
  "type": "stop_completed",
  "data": {"status": "success"}
}
```

---

### `break_stop_completed`
Sent when Python finishes processing a break-stop event.

```json
{
  "type": "break_stop_completed",
  "data": {"status": "success"}
}
```

---

### `auth_failed`
Sent when authentication finds no user in the Electron SQLite database.

```json
{
  "type": "auth_failed",
  "data": {"reason": "No user found in Electron database"}
}
```

---

### `version_check_result`
Sent in response to `check_updates_only` or `force_upgrade_update_app`.

```json
{
  "type": "version_check_result",
  "data": {
    "update_available": true,
    "current_version": "1.2.3",
    "latest_version": "1.3.0"
  }
}
```

---

### `timezone_error`
Sent when the local clock diverges from the org's configured timezone.

```json
{
  "type": "timezone_error",
  "data": {"message": "Local timezone does not match org timezone"}
}
```

---

### `subscription_expired`
Sent when the API signals the user's subscription has lapsed.

```json
{
  "type": "subscription_expired",
  "data": {"message": "..."}
}
```

---

### `sleep_auto_stop`
Sent when the system wakes from sleep and monitoring was auto-stopped.

```json
{
  "type": "sleep_auto_stop",
  "data": {"sleep_duration_seconds": 3600}
}
```

---

### `active_app_change`
Sent when the foreground application changes (if `appandurlstatus` enabled).

```json
{
  "type": "active_app_change",
  "data": {
    "app_name": "Visual Studio Code",
    "window_title": "app.py — workstatus",
    "timestamp": "2025-05-07 14:23:01"
  }
}
```

---

### `log`
Low-priority diagnostic log forwarded to Electron's devtools console.

```json
{
  "type": "log",
  "data": {"level": "info", "message": "Authentication complete"}
}
```

---

## Messages: Electron → Python

### `hello_from_electron`
Initiates the handshake. No data field required.

```json
{"type": "hello_from_electron"}
```

---

### `start`
Begin activity tracking for a project/task.

```json
{
  "type": "start",
  "data": {
    "projectId": 42,
    "taskId": 7
  }
}
```

Python queues this message if received before authentication completes
(deferred queue); it is processed immediately afterwards.

---

### `stop`
Stop activity tracking and flush pending data.

```json
{"type": "stop", "data": {}}
```

---

### `set_project_task`
Switch the active project/task without stopping and restarting tracking.

```json
{
  "type": "set_project_task",
  "data": {"projectId": 42, "taskId": 7}
}
```

---

### `get_monitoring_status`
Request current monitoring state. Python replies with `monitoring_status`.

```json
{"type": "get_monitoring_status"}
```

---

### `check_updates_only`
Check for a newer Python package version without installing.

```json
{"type": "check_updates_only"}
```

---

### `force_upgrade_update_app`
Download and install the latest Python package immediately.

```json
{"type": "force_upgrade_update_app"}
```

---

### `update_user_entity`
Push a fresh user/org object to Python (e.g. after org switch).

```json
{
  "type": "update_user_entity",
  "data": {
    "user_id": 123,
    "org_id": 5,
    "token": "...",
    "sd_token": "..."
  }
}
```

---

### `break_start` / `start_break`
Start a break period.

```json
{"type": "break_start", "data": {"break_id": 99}}
```

---

### `break_stop` / `stop_break`
End a break period.

```json
{"type": "break_stop", "data": {"break_id": 99}}
```

---

### `idle_popup_shown`
Inform Python that the idle popup was shown to the user.

```json
{"type": "idle_popup_shown"}
```

---

### `idle_popup_response`
User's response to the idle-time popup.

```json
{
  "type": "idle_popup_response",
  "data": {
    "action": "keep_time",
    "keep_idle_time": true
  }
}
```

---

### `add_note`
Attach a note to the current tracking interval.

```json
{
  "type": "add_note",
  "data": {"note": "Working on bug fix #1234"}
}
```

---

### `mark_idle_intervals_uploaded`
Inform Python that Electron has successfully uploaded idle intervals.

```json
{"type": "mark_idle_intervals_uploaded", "data": {"counter": 5}}
```

---

## Python Usage

```python
from ws_package.utils.electron_comm import get_electron_comm

ec = get_electron_comm()

# Send a message (no-ops gracefully if not connected)
ec.send_to_electron("tracking_error", {
    "code": "[E075]",
    "message": "Screenshot permission denied.",
    "recoverable": False,
    "source": "screenshot",
})

# Read the next queued message from Electron (non-blocking)
msg = ec.get_message(timeout=0.1)
if msg:
    msg_type = msg.get("type")
    data = msg.get("data")

# Check connection state
if ec.is_connected():
    ...
```

---

## Error Codes Reference

See [`ERRORS.md`](../../ERRORS.md) for the complete `[Exxx]` catalogue with
descriptions and remediation steps for every error code used in
`tracking_error` messages and log files.
