Remote relay
A deck built with kadal-deck build and put on a static host (GitHub Pages, Netlify, Vercel, S3,
a plain file server) has no live room and no dev server. The presenter view and the
phone remote are on two devices, so something has to carry messages between
them. kadal-deck relay is that something: a small WebSocket server that pairs one phone with one
presenting window and passes their messages along.
npx kadal-deck relaypnpm exec kadal-deck relayyarn kadal-deck relaybunx kadal-deck relayrelay ws://localhost:4174/ ws://192.168.1.20:4174/ password af35-f2qp-qdu8 (made up; --password <p> sets your own) the presenter view's Phone remote asks for the password once; a phone needs only the QR point a build at it: remote: { relay: "ws://192.168.1.20:4174/" } in the deck, or ?relay=ws://192.168.1.20:4174/ on its presenter view's address (Ctrl+C stops)You don’t need a relay for a deck published on deck.kadal.cc (its room pairs the phone) or for
kadal-deck dev (the dev server does). For the steps end to end, see
Present from your phone, even on a static host.
Options
Section titled “Options”| Option | Default | Meaning |
|---|---|---|
--port <n> |
4174 |
the port to listen on |
--host <addr> |
0.0.0.0 (127.0.0.1 with --tunnel) |
the address to listen on; 0.0.0.0 is every network interface |
--password <p> |
KADAL_DECK_RELAY_PASSWORD, else made up |
the password a presenting window must give |
--tunnel |
off | also put it on the internet through a Cloudflare Quick Tunnel and print its wss:// address |
--json |
off | print one JSON object once the relay (and its tunnel) is up |
Ctrl+C stops the relay and closes its tunnel. The relay keeps its pairings in memory, so after a restart the phone has to scan the QR code again.
Pointing a deck at it
Section titled “Pointing a deck at it”Either name the relay in the deck, which every build then uses:
---title: My talkremote: relay: wss://relay.example.com/---or leave the deck alone and add ?relay= to the presenter view’s address:
https://you.github.io/my-talk/presenter?relay=wss://relay.example.com/?relay= wins over the deck’s setting and is kept for that browser tab, so the windows the
presenter view opens (the share window, the notes) use it too, and the QR code carries it to the
phone. ?relay= with nothing after it forgets it. An https:// address is read as wss://, and
http:// as ws://.
A page served over https may only open a wss:// relay. Use ws:// addresses only for a deck you
open over plain http, such as kadal-deck serve on your own network.
The password
Section titled “The password”Only a presenting window needs the password; a phone needs only the code in the QR, exactly as on
kadal-deck dev.
- The first time the presenter view opens the Phone remote dialog, it asks for the relay’s password and keeps it for that tab.
- The password comes from
--password, else theKADAL_DECK_RELAY_PASSWORDenvironment variable, else the relay makes one up (three groups of four letters and digits) and prints it. - Wrong passwords are limited to 10 a minute from one address, and 100 a minute in all. After that the dialog says “Too many wrong passwords. Wait a minute.”
The password stops anyone else from using your relay to drive a window, or from running their own talk through it. The pairing itself works as everywhere else: a code that works once, changes every 30 seconds, one phone at a time.
What it serves
Section titled “What it serves”Nothing but the remote’s messages. It holds no deck, no notes, no drawings and no editing endpoints. A plain HTTP request to its root gets one line saying what it is; any other path is a 404. Messages are text frames of at most 64 KiB, and it pings every connection every 25 seconds to notice a dropped one.
Commands a phone sends run in the presenting window, which refuses what a phone may not do: open windows, go full screen, or pair and unpair phones.
Running it
Section titled “Running it”On your own network
Section titled “On your own network”For a talk in a room where your laptop and phone share a network, run the relay on the laptop and use the address it prints for that network:
npx kadal-deck relay --password stage-doorpnpm exec kadal-deck relay --password stage-dooryarn kadal-deck relay --password stage-doorbunx kadal-deck relay --password stage-doorServe the build on the same network (for example npx kadal-deck serve dist --host 0.0.0.0pnpm exec kadal-deck serve dist --host 0.0.0.0yarn kadal-deck serve dist --host 0.0.0.0bunx kadal-deck serve dist --host 0.0.0.0) and
open the presenter view with ?relay=ws://<laptop address>:4174/. The phone points by touch; the
air pointer needs https.
Through a tunnel
Section titled “Through a tunnel”For a deck on an https host, the relay must be wss://. The quickest way is a Cloudflare Quick
Tunnel, which needs cloudflared
installed and no account:
npx kadal-deck relay --tunnelpnpm exec kadal-deck relay --tunnelyarn kadal-deck relay --tunnelbunx kadal-deck relay --tunneltunnel wss://example-words-here.trycloudflare.com/ (a Cloudflare Quick Tunnel: for a deck on https) point a build at it: remote: { relay: "wss://example-words-here.trycloudflare.com/" } in the deck, or ?relay=wss://example-words-here.trycloudflare.com/ on its presenter view's addressA Quick Tunnel gets a new address every time it starts, so pass it with ?relay= rather than
building it into the deck.
On a server
Section titled “On a server”For an address that doesn’t change, run the relay on a small server behind a reverse proxy that terminates TLS and forwards WebSocket upgrades, and set the password from the environment:
KADAL_DECK_RELAY_PASSWORD='a long passphrase' npx kadal-deck relay --host 127.0.0.1 --port 4174# nginx: wss://relay.example.com/ → the relaylocation / { proxy_pass http://127.0.0.1:4174; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 1h;}Then put remote: { relay: "wss://relay.example.com/" } in the deck. One relay serves any number
of decks and talks at once: each presenting window gets its own codes and its own phone.
--json
Section titled “--json”With --json, the human-readable lines go to stderr and stdout carries one object when the relay
is ready:
{ "ok": true, "urls": ["ws://localhost:4174/", "ws://192.168.1.20:4174/"], "tunnel": null, "password": "af35-f2qp-qdu8"}password is null when you gave one (it is never echoed), and tunnel is the wss:// address
with --tunnel. The command keeps running until it is stopped. Exit codes are the CLI’s: 2 for a
bad flag (an empty --password), 4 when --tunnel can’t find cloudflared.