Skip to main content

Python asyncio port of the PeerJS signalling server

Project description

python-peerjs-server

An asyncio-based Python port of the PeerJS signalling server, the WebSocket broker that PeerJS clients use to discover each other and exchange WebRTC offers/answers/candidates.

It's feature-complete and wire-compatible with the original: WebSocket session handling, reconnect, and the HTTP API are all implemented, plus a few deployment conveniences, like rate limiting, proxied-IP support, and bounded queues.

Features

  • WebSocket signalling at {path}/peerjs with id/token/key query-param auth
  • Reconnect support (same id + token re-attaches the socket and drains queued messages)
  • Per-peer message queueing for offline destinations, with periodic EXPIRE sweeps
  • Periodic dead-connection reaping based on heartbeat pings
  • HTTP API: GET {path}{key}/id (fresh peer id) and GET {path}{key}/peers (peer list, opt-in via allow_discovery)
  • CORS header support
  • Fixed-window per-IP rate limiting on the HTTP API
  • Optional X-Forwarded-For trust for real client IPs when running behind a reverse proxy

Requirements

  • Python 3.13+
  • websockets>=14 (the only runtime dependency, installed automatically below)

Installation

pip install python-peerjs-server

For local development (editable install with test/lint tooling):

pip install -e ".[dev]"

Usage

Run as a console script:

peerjs-server --port 9000 --key peerjs

or as a module:

python -m python_peerjs_server --port 9000 --key peerjs

CLI options

Flag Env var Default Description
--host PEERJS_HOST :: Bind host
--port PEERJS_PORT 9000 Bind port
--key PEERJS_KEY peerjs Access key clients must supply
--path PEERJS_PATH / Path prefix for HTTP/WS routes
--allow-discovery PEERJS_ALLOW_DISCOVERY off Expose GET /peers
--cors-origin PEERJS_CORS_ORIGIN unset CORS origin for HTTP API responses
--log-level PEERJS_LOG_LEVEL INFO Logging level

Other server behaviour (timeouts, rate limits, queue caps, proxy trust) is configured via python_peerjs_server.config.Config when embedding the server programmatically; see src/python_peerjs_server/config.py for the full set of fields and their defaults.

Embedding in your own asyncio app

import asyncio
from python_peerjs_server.config import Config
from python_peerjs_server.main import peerjs_serve

asyncio.run(peerjs_serve(Config(port=9000, key="peerjs")))

Logging

If you're running via the CLI, --log-level/PEERJS_LOG_LEVEL already configures this for you (it calls logging.basicConfig); the rest of this section is for embedding python_peerjs_server in your own app.

Every module logs under the python_peerjs_server namespace (logging.getLogger(__name__)), so that one logger name is the knob to turn:

import logging

# See python_peerjs_server logs at INFO+ on stderr
logging.getLogger("python_peerjs_server").setLevel(logging.INFO)
logging.getLogger("python_peerjs_server").addHandler(logging.StreamHandler())

# Or, if your app already configured the root logger, just set the level;
# propagation does the rest
logging.getLogger("python_peerjs_server").setLevel(logging.DEBUG)

# Silence a noisy sub-logger specifically
logging.getLogger("python_peerjs_server.services.web_socket_session").setLevel(logging.WARNING)

A NullHandler is attached by default, so the library stays silent unless logging is configured (either on python_peerjs_server or higher up).

Connecting a PeerJS client

This server only implements the signalling protocol; clients connect using the PeerJS client library (docs, GitHub):

const peer = new Peer("some-id", {
    host: "localhost",
    port: 9000,
    path: "/",
    key: "peerjs",
});

Runnable versions of the standalone and FastAPI embedding patterns live in examples/: standalone.py and fastapi_app.py.

CORS

The PeerJS client calls the HTTP API (GET {path}{key}/id) before it opens the WebSocket. If your page is served from a different origin than the broker (e.g. a Vite/CRA dev server on localhost:5173 talking to a broker on localhost:9000), the browser will block that request with a CORS error unless you set --cors-origin / PEERJS_CORS_ORIGIN / Config.cors_origin to the exact origin (scheme + host + port) serving the page:

peerjs-server --port 9000 --key peerjs --cors-origin http://localhost:5173

It's unset by default (no Access-Control-Allow-Origin header sent), which is fine for same-origin deployments.

Running alongside another web framework

peerjs_serve() is just a coroutine; you can run it inside whatever process already hosts your app instead of standing up a separate service. Pass handle_signals=False so it doesn't fight your app for SIGINT/SIGTERM handling, and pick a port distinct from your main app's.

Runnable versions of both patterns below live in examples/: tornado_app.py and flask_app.py.

Tornado

Tornado runs on the standard asyncio event loop, so the broker can simply be scheduled as another task on it:

import asyncio
import tornado.ioloop
import tornado.web

from python_peerjs_server.config import Config
from python_peerjs_server.main import peerjs_serve


class MainHandler(tornado.web.RequestHandler):
    def get(self) -> None:
        self.write("hello from tornado")


async def main() -> None:
    app = tornado.web.Application([(r"/", MainHandler)])
    app.listen(8888)

    asyncio.create_task(peerjs_serve(Config(port=9000, key="peerjs"), handle_signals=False))

    await asyncio.Event().wait()  # run forever


if __name__ == "__main__":
    asyncio.run(main())

Flask

Flask's dev server is sync/WSGI and blocks its own thread, so the broker has to run on an event loop in a separate thread:

import asyncio
import threading

from flask import Flask

from python_peerjs_server.config import Config
from python_peerjs_server.main import peerjs_serve

app = Flask(__name__)


@app.get("/")
def index() -> str:
    return "hello from flask"


def run_python_peerjs_server() -> None:
    asyncio.run(peerjs_serve(Config(port=9000, key="peerjs"), handle_signals=False))


if __name__ == "__main__":
    threading.Thread(target=run_python_peerjs_server, daemon=True).start()
    app.run(port=8888)

For an ASGI app (FastAPI, Starlette) you can instead start it from a lifespan/startup hook with asyncio.create_task, the same way as the Tornado example: both share a loop with no extra thread needed.

Development

pip install -e ".[dev]"  # install with dev/test/lint dependencies
pytest                 # run tests
ruff check src         # lint
ruff format src        # format
mypy src                # type check
bandit -r src           # security scan

License

MIT, see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

python_peerjs_server-1.0.0b2.tar.gz (24.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

python_peerjs_server-1.0.0b2-py3-none-any.whl (20.2 kB view details)

Uploaded Python 3

File details

Details for the file python_peerjs_server-1.0.0b2.tar.gz.

File metadata

  • Download URL: python_peerjs_server-1.0.0b2.tar.gz
  • Upload date:
  • Size: 24.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for python_peerjs_server-1.0.0b2.tar.gz
Algorithm Hash digest
SHA256 3b9930db0443c509f917389e37c6e7634efc6c9f31e14a2272c110f41b62f9dd
MD5 240e869851d586c8fddd220e2381cea3
BLAKE2b-256 26d5947adc6814dda682261e597dca1b674057b0c0e6d99752a775f250bd5009

See more details on using hashes here.

File details

Details for the file python_peerjs_server-1.0.0b2-py3-none-any.whl.

File metadata

File hashes

Hashes for python_peerjs_server-1.0.0b2-py3-none-any.whl
Algorithm Hash digest
SHA256 3fcb8c2f5ba583b7e8cccd6ee8144f4e5dd37313096e2926a4da20e068dd0a65
MD5 08bbcd02160377b393b327b47b7385d3
BLAKE2b-256 2c30b73646f6747ec2e1a63fb8b7206af096d9dacf3b4a71f6946ce550095dfe

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page