Files
Veskslop-t/docs/core-bridge.md
T

130 lines
5.0 KiB
Markdown

# Core bridge
The core bridge is the only surface between the Vesktop core and the external daemon. This document
describes the protocol, the validation rules, and the hooks into upstream files.
## Files
| File | Role | Status |
| --- | --- | --- |
| `src/shared/puppetProtocol.ts` | Action names, error codes, limits, and shared types | Implemented |
| `src/renderer/puppet/actions.ts` | Validated action handlers and the core rate limit | Implemented |
| `src/renderer/puppet/events.ts` | Event normalization and push to main | Pending |
| `src/renderer/puppet/index.ts` | Renderer initialization | Pending |
| `src/main/puppetBridge.ts` | Unix socket server, request forwarding, event fan-out | Pending |
## Transport
The core bridge uses a Unix socket. On Linux the path is `$XDG_RUNTIME_DIR/vesktop-puppet.sock`.
On Linux without `XDG_RUNTIME_DIR`, the path is `/tmp/vesktop-puppet.sock`. On Windows the path is
the named pipe `\\.\pipe\vesktop-puppet.sock`.
The protocol is newline-delimited JSON. Each request gets one response line. An event subscriber
gets event lines until the connection closes.
Request:
```json
{ "id": 1, "action": "message.send", "params": { "channelId": "123", "content": "hello" } }
```
Success response:
```json
{ "id": 1, "ok": true, "result": { "channelId": "123", "sent": true } }
```
Error response:
```json
{ "id": 1, "ok": false, "error": { "code": "CONTENT_EMPTY", "message": "content is empty" } }
```
Event line:
```json
{ "type": "event", "event": { "type": "MESSAGE_CREATE", "channelId": "123" } }
```
## Actions
| Action | Parameters | Result |
| --- | --- | --- |
| `ping` | none | `"pong"` |
| `events.subscribe` | none | `{ "subscribed": true }`. The socket becomes an event subscriber. |
| `state.get` | none | The current user, the guilds, and the channels. Cache-only. |
| `message.send` | `channelId`, `content` | `{ channelId, sent: true }` |
| `message.read` | `channelId`, `limit` | `{ cached, messages }`. Cache-only. |
| `dm.send` | `userId`, `content` | `{ userId, channelId, sent: true }` |
## Validation rules
The core validates all input. The daemon cannot bypass these rules.
- A channel ID and a user ID must match the snowflake format. The format is 17 to 20 digits.
- `content` must be a string. The content must not be empty after a trim.
- `content` must not contain control characters.
- `content` must not be longer than 2000 characters.
- The target channel must exist. The channel type must accept text. The allowed types are
`GUILD_TEXT`, `DM`, `GROUP_DM`, `GUILD_ANNOUNCEMENT`, `ANNOUNCEMENT_THREAD`, `PUBLIC_THREAD`,
and `PRIVATE_THREAD`.
- `limit` must be an integer from 1 to 100.
- A request line must not be larger than 64 KiB.
## Core rate limit
The core rate limit is a safety net. It does not replace the daemon pacing.
- The minimum interval between two outbound actions is 500 ms.
- The maximum is 30 outbound actions per minute.
- The limit applies to `message.send` and `dm.send`.
## Error codes
| Code | Meaning |
| --- | --- |
| `INVALID_REQUEST` | The line is not valid JSON. |
| `PAYLOAD_TOO_LARGE` | The request line is larger than 64 KiB. |
| `UNKNOWN_ACTION` | The action name is not in the action list. |
| `CORE_UNAVAILABLE` | The bridge cannot reach the renderer. |
| `NOT_LOGGED_IN` | No user is logged in. |
| `INVALID_SNOWFLAKE` | The ID is not a snowflake. |
| `INVALID_PARAM` | A parameter has the wrong type or value. |
| `CONTENT_EMPTY` | The content is empty. |
| `CONTENT_TOO_LONG` | The content is longer than 2000 characters. |
| `CHANNEL_NOT_FOUND` | The channel is not in the client cache. |
| `NOT_SENDABLE` | The channel type cannot receive messages. |
| `SEND_FAILED` | Discord rejected the message. |
| `DM_UNAVAILABLE` | The client cannot create a DM channel. |
| `RATE_LIMITED` | The core rate limit rejected the action. |
| `INTERNAL` | An unexpected error occurred. The details go to the local log only. |
## Hooks into upstream files
The bridge uses a small number of hooks. Keep these changes minimal.
| File | Change |
| --- | --- |
| `src/main/main.ts` | Import the bridge. Call `initPuppetBridge()` after `app.whenReady()`. |
| `src/shared/IpcEvents.ts` | Add `PUPPET_EVENT` and `IpcCommands.PUPPET_COMMAND`. |
| `src/renderer/index.ts` | Import `./puppet`. |
| `src/renderer/ipcCommands.ts` | Register `PUPPET_COMMAND` and call `handlePuppetAction`. |
| `src/preload/VesktopNative.ts` | Add `puppet.emit(event)` for event push. |
## How to add an action
1. Add the action name to `PuppetAction` in `src/shared/puppetProtocol.ts`.
2. Add a validation case in `src/renderer/puppet/actions.ts`.
3. Add the action to the known-action set in `src/main/puppetBridge.ts`.
4. Add a tool in the daemon (`poc/src/control-bridge.ts`).
5. Add a unit test for the validation path.
6. Run `pnpm testTypes`, `pnpm lint`, and `pnpm build`.
## Why this shape
The shape gives three properties:
- The daemon cannot run arbitrary code in the client.
- A daemon bug cannot crash the client or send malformed data to Discord.
- The core patch stays small, so an upstream rebase is easy.