Skip to content

Activities ​

An activity is a web page that people in a call open together: a game, a whiteboard, a watch-together player, a quiz. Refract runs it in a sandboxed frame in the call and passes messages between everyone's copies, so an activity needs no server of its own to stay in step.

Installing activities ​

Instance admins manage them under Manage instance → Activities:

  • Packaged with Refract: activities that ship with the server. Install adds one in a click. Two are included: Basic, a shared button with a debug sidebar that shows every message (the place to start building), and Watch together, a video played in step for everyone.
  • Add an activity: one hosted anywhere else, by its name, address and a short description. The address must:
    • use https://. Pages on localhost are allowed on development instances, or with developer mode on, for building one;
    • not be on the instance's own site;
    • be the page's final address, after any redirects, since only messages from that origin are accepted.

Refract adds each activity's origin to its Content-Security-Policy, so people see a new one the next time they load the app. If you set CSP_DIRECTIVES yourself, add the origins to its frame-src.

After an activity's page changes, Refresh reloads it in every call that has it open.

Using one ​

Anyone in a call opens an activity from the Activities button in the call panel. The others in the call are invited to join. One activity runs per call at a time, as a tile on the call's stage, like a screen share. It closes for each person when they leave it, and for everyone when the last person leaves or the call ends.

Building one ​

An activity is a static page that loads refract-activity.js, a small module every instance serves at /activities/refract-activity.js. Copy it (and refract-activity.css, for Refract's look) into your page's folder:

js
import { connectToRefract } from "./refract-activity.js";

const refract = connectToRefract();
const { self, participants, theme } = await refract.ready;

refract.onMessage((from, data) => {
  if (data.type === "move") applyMove(from, data.square);
});
refract.onParticipants((participants, { joined, left }) => showPeople(participants));

await refract.broadcast({ type: "move", square: "e4" });
MemberWhat it is
readyResolves when Refract answers, with self, participants, theme ("dark" or "light") and IDs
selfThe person using this copy: { id, name, avatarUrl }
participantsEveryone with it open, in the order they joined
leaderWhoever has had it open longest. Every copy agrees, so it's the one to do work only one should do
broadcast(data, to?)Sends JSON to everyone else, or only the user IDs in to
onMessage((from, data) => …)Messages from the other copies
onParticipants(…)When someone opens or closes it
setFullscreen(on)Asks Refract to show the page full screen. Call it from a click or key press
close()Leaves the activity
  • Messages are JSON of up to 16 KiB, about 20 a second per person (bursts of up to 40). They aren't stored, and your own aren't echoed back.
  • Late joiners have missed what came before. Have each new page send a hello once it's ready, and have the leader answer it with the current state.
  • What others send is untrusted. Check its shape, and show names and messages as text, never HTML.
  • The page holds no credentials. Everything goes through the person's own signed-in connection, so a page can only ever act as the person using it.

Hosting it ​

Any static host over HTTPS works, such as GitHub Pages, GitLab Pages or Netlify. To start, copy the Basic activity's folder with refract-activity.js and refract-activity.css beside it, and change its imports from ../ to ./.

To host one beside your instance, docker-compose.override.example.yaml has an activity-chess service: a plain nginx container on a host name of its own, serving the activity's folder.

To build one locally, serve the page on http://localhost, add it on a development instance, and open it in a call from two browsers signed in as different people.

Released under the GNU Affero General Public License v3.0.