Skip to content

Stream events

Alexandre 'kidev' Poumaroux edited this page Oct 21, 2026 · 2 revisions

Stream events

ProLeak can send its events and log lines to programs outside the game: an OBS overlay, a Twitch bot, a script. The stream is a WebSocket on 127.0.0.1. It is off until you turn it on, and a slow client never slows the game.

Contents

Turning it on

In the game, Mods > Advanced > Turn on the stream. The setting is saved, so the stream starts with the game from then on. In ProLeak/config/proleak.yml:

stream: true              # listen when the game starts
stream_port: 47600        # on 127.0.0.1
stream_origins: ["null"]  # browser pages allowed to connect (see Access)

While the stream listens, ProLeak keeps ProLeak/data/stream.json:

{"port": 47600, "token": "3f9a0c..."}

The file is removed when the stream stops. The token is made the first time the stream starts and kept, as stream_token in proleak.yml, until you press New token in Mods > Advanced. A new token disconnects every client that used the old one; clients connect again with the new token from stream.json.

Access

  • Every client sends the token in the address: ws://127.0.0.1:47600/?token=<token>. A missing or wrong token is refused with HTTP 403.
  • A browser adds an Origin header naming the page that connects. Only the origins listed in stream_origins get in; a page loaded from a local file (an OBS browser source pointing at a file) has the origin null. Programs that send no Origin, such as the Python client, need only the token. Every local file and every sandboxed frame on any website has the origin null too, so once null is listed only the token keeps other pages out.
  • At most 8 clients connect at once.
  • Anyone who can read the game folder can read stream.json, and with it the stream (events only: the stream takes no commands).

Messages

Every message is a JSON text frame. On connecting, the server says hello and lists the events a client can ask for:

{"type": "hello", "protocol": 1, "proleak": "0.1.0", "events": ["chat.received", "unit.built", "..."]}

Nothing more arrives until the client subscribes. A subscription replaces the previous one:

{"type": "subscribe", "events": ["wave.*", "unit.built"], "log": true}

In a pattern, * stands for one or more whole name parts: wave.* takes wave.started and wave.ended. The game events can be streamed; raw network events (net.*) cannot.

Then the server sends:

type Fields
event name, phase (pre or post), cancelled, frame, time (UTC), fields (as the events reference lists them; units and players by id)
log time, level, source (a plugin id, or core), message
dropped count: messages this client missed since the last report
error message: why a request was refused. After a refused subscription the connection stays open; after a malformed frame (binary, fragmented, or larger than 64 KB) it closes

Each event arrives once for its post phase, and also for its pre phase when a plugin handles that phase. A subscription takes at most 64 patterns.

A client that reads slowly loses messages rather than holding the game back, and gets a dropped message before the next one it receives. A client that accepts no data for 10 s is disconnected.

An OBS overlay

This overlay is an HTML file shown by an OBS browser source. Its URL carries the token, file:///path/overlay.html?token=<token>, copied once from stream.json (again after a New token), and stream_origins lists "null":

<!doctype html>
<meta charset="utf-8">
<body style="color: white; font: 24px sans-serif">
<div id="wave"></div>
<script>
  const token = new URLSearchParams(location.search).get("token");
  const ws = new WebSocket(`ws://127.0.0.1:47600/?token=${token}`);
  ws.onopen = () => ws.send(JSON.stringify({type: "subscribe", events: ["wave.started"]}));
  ws.onmessage = (m) => {
    const msg = JSON.parse(m.data);
    if (msg.type === "event") {
      document.getElementById("wave").textContent = `Wave ${msg.fields.wave}`;
    }
  };
</script>

Python

proleak-client reads stream.json itself:

import asyncio
import proleak_client

async def main():
    async with proleak_client.connect() as client:
        await client.subscribe(["unit.built"])
        async for message in client:
            print(message["name"], message["fields"])

asyncio.run(main())

Previous: Replay your games | Next: Local games

Clone this wiki locally