Python reference: wordplus-realtime
The package needs Python 3.9 or later, with PyNaCl. It takes the same options as the Node and PHP packages, and answers the browser the same way.
Realtime( site, secret, local_key, **options )
Realtime.from_environment( **options ) is the same, with site, secret and local_key taken from WORDPLUS_SITE, WORDPLUS_SECRET and WORDPLUS_LOCAL_KEY.
| Option | |
|---|---|
site, secret | your app's key and secret |
local_key | 64 hex characters of your own (encryption) |
profile | lambda user: {"name": …, "avatar": …, "url": …} or None: the user's directory entry, and by default their presence info and call info |
has_keys | lambda user: False cuts a user off every key |
generations | a GenerationStore, for rotate_channel() and rotate_user() |
seal | False sends everything plain |
base | where your routes are, /api/realtime by default, for config() |
headers | headers the browser sends to your routes, such as a CSRF token, for config() |
worker, rooms | where your app serves the two files, for config() |
token_ttl, call_token_ttl | seconds: 6 hours by default; a channels token lasts at most a day, a calls token at most 6 hours |
server, api | WordPlus Cloud by default |
http | (method, url, headers, body) -> (status, body), your own client for publishing |
A user is the id your app gives, an integer or a string, or None for nobody. In this version, calls and the directory take integer ids only.
Owners
| Method | |
|---|---|
channel(prefix, lambda user, channel: answer) | private and presence channels, by the start of their names; answers False, True, or {"id": …, "info": …} (channels, presence) |
calls(prefix, lambda user, call, with_user: answer) | calls and rooms; answers False, True, or {"info", "publish", "subscribe", "admin", "hidden"} (calls) |
kind(kind, lambda rest, user, label: bool, newest=None) | keys of your own kind (encryption) |
The longest prefix that fits decides. The callbacks are plain functions, not async. Each method returns the instance, so calls chain.
Routes
| Method | |
|---|---|
answer_request(method, content_type, action, body, user) | → (status, body): one route, auth, calls or keys, for a request's raw body |
handle(action, body, user) | → (status, body), for a body already decoded |
wordplus_realtime.django.urls(realtime, user=…) | the routes in Django (Django) |
wordplus_realtime.fastapi.router(realtime, user) | the routes in FastAPI and Starlette (FastAPI) |
The routes take JSON POSTs only, and answer anything else with 415. A refusal is part of the answer (denied), not an HTTP error.
| Method | What it answers |
|---|---|
authorize(channels, user) | {"token", "exp", "keys", "denied"} |
authorize_call(request, user) | {"token", "exp", "key"} or {"denied": …} |
keys_answer(labels, user) | {"keys", "identity"?} |
identity(user) | {"token", "exp"} or None |
token(channels, user=None, info=None, ttl=None) | a channels token, signed as the auth route signs |
config(user=None, headers=None) | the browser's settings for connect(), with a signed-in user's keys and identity |
may_join(channel, user), may_have(label, user) | what the routes decide, for your own checks |
Publishing
| Method | |
|---|---|
publish(channels, event, data=None, *, except_tab=None, seal=True) | one channel, or up to 100; data up to 10 KB of JSON (publishing) |
await apublish(…) | the same from async code, on a worker thread |
publish_batch([{"channels", "event", "data", "except", "seal"}, …]) | 10 events a request |
channel_info(channel) | {"subscriptions", "members"?} |
rotate_channel(channel), rotate_user(user) | moves a key on; needs generations |
They raise RealtimeError when WordPlus Cloud refuses: e.code holds the code, and e.status the HTTP status.
Keys
realtime.keys is the app's Keys: seal(text, label), seal_json(data, label), open(value), entry(label), day_label(), channel_label(channel), user_label(user).
Generations
class GenerationStore(Protocol):
def get(self, name: str) -> Optional[int]: ...
def set(self, name: str, generation: int) -> None: ...
Keep them where all your servers see them, and for good: a database row, or a cache that doesn't evict.
Helpers
channel_kind(channel) gives public, private or presence. valid_channel(channel) says whether WordPlus Cloud takes the name. label_of(value) names the key a sealed value needs. All three come from wordplus_realtime.