Executor bridge — Port, Stream, and Compat
Synapse Framework connects the desktop UI to a Roblox executor through a local HTTP bridge on 127.0.0.1:31337. Three transport methods share one Axum server; Settings chooses which queue and Lua client the UI uses.
| Settings label | bridgeMethod | Lua client | Rust module |
|---|---|---|---|
| Fastest | port | Port Bridge.lua | src-tauri/src/port_bridge.rs |
| Stream | stream | Stream Bridge.lua | src-tauri/src/stream_bridge.rs |
| Compatible | compat | Compat Bridge.lua | src-tauri/src/compat_bridge.rs |
Legacy WebSocket (/ws, Websocket Bridge.lua) has been removed. Saved settings with bridgeMethod: "websocket" are migrated to port automatically.
Overview
flowchart LR UI["Synapse UI"] -->|bridge_send_execute| Rust["bridge.rs"] Rust --> PortQ["port_pending_execs"] Rust --> StreamQ["stream_pending_execs"] Rust --> CompatQ["compat_pending_execs"] PortQ --> PortLua["Port Bridge.lua"] StreamQ --> StreamLua["Stream Bridge.lua"] CompatQ --> CompatLua["Compat Bridge.lua"] PortLua <-->|HTTP :31337| Axum["Axum server"] StreamLua <-->|HTTP :31337| Axum CompatLua <-->|HTTP :31337| Axum
- Server: Axum on
http://127.0.0.1:31337(spawned fromsrc-tauri/src/lib.rs). - Setting:
bridgeMethodinsrc/app/appSettings.ts— available in every UI shell’s Settings/Options. - Scripts: Bundled in
src-tauri/resources/scripts/andsrc/assets/editor-sidebar-scripts/(synced vianpm run sync:bridge-scripts).
Attach flow
- Click Attach in the Synapse UI.
- Run the matching bridge script in your executor:
- Sidebar:
Port Bridge.lua,Stream Bridge.lua, orCompat Bridge.lua - Or download from the local server (see below).
- Sidebar:
- The UI shows Connected when the Rust side reports
*_connected: truefor the selected method.
Script download URLs
| URL | File served |
|---|---|
GET http://127.0.0.1:31337/port_bridge.lua | Port Bridge.lua |
GET http://127.0.0.1:31337/stream_bridge.lua | Stream Bridge.lua |
GET http://127.0.0.1:31337/compat_bridge.lua | Compat Bridge.lua |
GET http://127.0.0.1:31337/bridge.lua | Port Bridge.lua (alias) |
GET http://127.0.0.1:31337/health | ok |
Execute flow (UI → executor)
- User clicks Execute (or auto-execute runs).
- Frontend calls Tauri
bridge_send_executewith{ source, method }wheremethodisport,stream, orcompat. - Rust enqueues the script (base64) on the method-specific pending queue.
- The Lua client polls
*/next, receives the payload, runs it in the game, and posts*/result. - UI receives
synapse:bridge-execute-result; errors and logs appear in the F9 console viasynapse:bridge-log.
Central dispatch lives in src/app/executorBridge/bridgeDispatch.ts. Shells use useBridgeExecuteReady() for execute enable/disable.
Status and events
| IPC / event | Purpose |
|---|---|
bridge_status | Snapshot: port_*, stream_*, compat_*, legacy_disabled |
synapse:bridge-status | Pushed status updates to all webviews |
synapse:bridge-execute-result | Execute outcome { id, ok, error? } |
synapse:bridge-log | F9 console lines from executor or plugins |
legacy_disabled: true means a plugin bridge provider has taken over — Port/Stream/Compat execute is blocked in Rust until legacy is re-enabled.
Plugin bridge providers
Plugins with the bridge permission may register a bridge provider that replaces legacy execute when active:
host.setLegacyBridgeDisabled(true)setslegacy_disabledin Rust.- Only one provider owns execute at a time; legacy Port/Stream/Compat is unavailable while it owns the bridge.
- Undocked V3 tabs and Script Hub webviews do not bootstrap plugins; they relay provider execute to the main window via
dispatchBridgeExecuteViaMainwhen the provider owns the bridge (src/app/executorBridge/useBridgeExecuteReady.ts).
See PLUGINS.md for the generic bridge-provider API (no vendor-specific plugins documented here).
Port (Fastest) — default
Best for: Almost every executor — only requires game:HttpGet. Uses request() (or syn.request / http_request) for POST when available.
Base URL: http://127.0.0.1:31337/port_bridge
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET / POST | /port_bridge/hello | Client identification; sets port_connected |
| GET | /port_bridge/ping | Lightweight liveness (no status emit) |
| GET | /port_bridge/next | Long-poll (≤10s) for execute payload(s) |
| GET / POST | /port_bridge/result | Return execute result (error_b64 on GET for HttpGet-only) |
| GET / POST | /port_bridge/log | Stream a log line to F9 |
Behavior
- Long-poll on
next: Server holds the GET up to ~10 seconds when idle; delivers executes immediately when queued. Steady-state polling acts as a heartbeat. - Liveness: Watchdog clears
port_connectedafter ~45s without client traffic (3 consecutive stale ticks). - Batching: Up to 16 pending executes per
nextresponse when the queue is backed up. - HttpGet fallback: Result and log endpoints support GET with base64 query params when POST is unavailable.
When to use
- Default choice for new setups.
- Executors with weak or missing
request()support. - Lowest executor requirements of the three methods.
Source: src-tauri/src/port_bridge.rs, src/assets/editor-sidebar-scripts/Port Bridge.lua
Stream
Best for: Executors with reliable request() / HTTP client support. Optional NDJSON heartbeat stream; execute delivery uses the same long-poll contract as Port.
Base URL: http://127.0.0.1:31337/stream_bridge
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET / POST | /stream_bridge/hello | Client identification; sets stream_connected |
| GET | /stream_bridge/ping | Lightweight liveness |
| GET | /stream_bridge/stream | NDJSON heartbeat only (does not drain executes) |
| GET | /stream_bridge/next | Long-poll (≤10s) for execute payload(s) |
| GET / POST | /stream_bridge/result | Execute result |
| GET / POST | /stream_bridge/log | Log line |
Behavior
/stream_bridge/stream: Keepsstream_connectedalive with periodic NDJSON heartbeats (~15s). Does not deliver scripts — prevents stray push clients from stealing payloads without dispatching them.- Execute delivery: Same as Port — long-poll on
/stream_bridge/next. - Liveness: ~45s idle timeout (same watchdog pattern as Port).
- Batching: Up to 16 executes per
nextresponse.
When to use
- Executor has
request()and you want Stream’s heartbeat stream for connection stability. - Port works but you prefer the Stream client’s polling model.
Source: src-tauri/src/stream_bridge.rs, src/assets/editor-sidebar-scripts/Stream Bridge.lua
Compat (Compatible)
Best for: Executors that only support game:HttpGet and cannot hold long HTTP connections (long-poll fails or times out aggressively).
Base URL: http://127.0.0.1:31337/compat_bridge
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET / POST | /compat_bridge/hello | Client identification; sets compat_connected |
| GET | /compat_bridge/ping | Lightweight liveness |
| GET | /compat_bridge/next | Immediate response (exec or null) — no server-side long-poll |
| GET / POST | /compat_bridge/result | Execute result (GET + base64 params) |
| GET / POST | /compat_bridge/log | Log line |
Behavior
- Short-poll: Client polls
/compat_bridge/nextevery ~0.5s; server returns immediately. - All traffic via GET where needed (base64-encoded query parameters).
- Liveness: Shorter idle timeout — ~15s without client traffic.
- Batching: Up to 16 executes per
nextresponse when multiple are queued.
When to use
- Port long-poll disconnects or never shows Connected.
- Executor blocks or kills requests that stay open for several seconds.
- HttpGet-only environment with no usable
request().
Source: src-tauri/src/compat_bridge.rs, src/assets/editor-sidebar-scripts/Compat Bridge.lua
Choosing a method
| Situation | Recommended method |
|---|---|
| Not sure / first try | Port (Fastest) |
| Port disconnects on long HTTP holds | Compat |
Strong request() support, want stream heartbeat | Stream |
| Plugin bridge provider active | Legacy methods disabled — use the provider |
Switch methods in Settings → Bridge method. After switching, re-run the matching Lua script in your executor.
Undocked tabs and Script Hub
Secondary webviews (V3 undock, Script Hub) share bridge state via:
synapse:bridge-statusevents from Rustsynapse.bridgeSession.v1in localStorage (published by the main window)
- Legacy (Port/Stream/Compat): Execute calls
bridge_send_executedirectly — the bridge server is global, not per-window. - Plugin provider: Undock relays execute to main when
legacy_disabledis true and the session reports the provider connected.
Key source files
| Concern | Path |
|---|---|
| Router + execute dispatch | src-tauri/src/bridge.rs |
| Port transport | src-tauri/src/port_bridge.rs |
| Stream transport | src-tauri/src/stream_bridge.rs |
| Compat transport | src-tauri/src/compat_bridge.rs |
| Bridge settings | src/app/appSettings.ts |
| UI bridge context | src/app/executorBridge/ExecutorBridgeContext.tsx |
| Central execute routing | src/app/executorBridge/bridgeDispatch.ts |
| Connection display | src/app/executorBridge/bridgeConnectionDisplay.ts |
| Lua clients | src/assets/editor-sidebar-scripts/*.lua |
| Script sync to Tauri resources | scripts/sync-bridge-scripts.mjs |
Internal legacy note
bridge.rs still exposes /matcha/* HTTP routes for an older internal HTTP client path. This is not a user-facing bridge method and is not selectable in Settings. Use Port, Stream, or Compat instead.