Skip to content

Developers

Extensions

This is how tenants extend the product. Modules and calendars and tasks are built.

Two kinds of extension stand side by side:

  • Modules are small web apps that their authors host. The client shows their pages in sandboxed frames at fixed places, and the pages talk to the room through the scripting API. A module that needs a server side uses the partner API and webhooks. No module code runs on the product's servers.
  • Calendars and tasks are built into the product: a person connects a Google or Microsoft account and sees their meetings and tasks in the room.

Modules

What a module is

A module is a manifest, a JSON document at an https address, and the pages it names. Its author hosts both. schema/module.schema.json defines the manifest, and examples/module is a module to start from.

FieldContents
version1, the version of the manifest's format
idthe module's name for itself, lowercase letters, digits and -, up to 40, unique within a tenant
name, description, iconhow the administration and the client present it
releasethe author's version of the module
permissionsthe parts of the scripting API its pages may call, below
panela page that opens in the side panel, with its title and width
toolbarup to 4 buttons for the bottom bar or its apps menu, each with a label and an icon, opening the panel or a page in a dialog
menuup to 4 entries of the account menu, each opening the panel or a page in a dialog
areasup to 10 kinds of map area the module adds, below
serverthe partner API scopes and webhook topics its server side needs, if it has one

Addresses in the manifest are absolute, or relative to the manifest's own address, and must use https; a development server takes http too. A manifest is at most 64 KiB, and a tenant has at most 20 modules.

Pages

Every page of a module loads in an iframe sandboxed as the websites of map areas are, from the module's own origin, so it reads nothing of the room's page and cannot navigate it. The client adds the room's origin to the page's address in the query parameter world, from which the page loads the scripting API, <world>/scripting/v1/world.js; its calls then go through the API's messages (Scripting API). world.module tells a page which module it belongs to and, for an area page, the area's settings. examples/module/world.js loads the API this way.

Permissions

A module asks for permissions by the API's namespaces, and its pages may call only what it was granted; anything else fails with forbidden.

PermissionMembers of world
roomroom, players and the areas of room, read only
playerplayer: moving, status, speech and outline
statestate and player.state
chatchat
uiui: popups, banners, overlays and menus of its own
controlscontrols
soundsound and camera
navnav: going to other rooms and opening tabs
meetingsmeetings and spaces
eventsevents

Map scripts keep the whole API, as their map's authors are the tenant's own.

Errors

The server checks a manifest against schema/module.schema.json and answers what is wrong as the map format does, with a code for each rule (Map format):

CodeChecked byRule
module.documentschemathe manifest misses version, id, name or permissions, or has a field the format does not know
module.idschemaan id that is not lowercase letters, digits and dashes, up to 40
module.labelschemaa name, title or label that is empty or longer than 60 characters
module.page-urlschemaan address that is neither http(s) nor relative
module.permissionschemaa permission the API does not know
module.panelschemaa panel without its address or title, or of a width outside 20 to 100 percent
module.opensschemaa button or an entry that opens neither the panel nor a page with a title
module.buttonschemaa toolbar button without its id, label or what it opens
module.entryschemaa menu entry without its id, label or what it opens
module.areaschemaan area kind without its type, name or page
module.serverschemaan unknown scope or topic, or topics without the webhook that hears them
module.addressfetchan address that does not use https outside development, or that the server cannot fetch
module.sizefetcha manifest over 64 KiB, or one that is not JSON
module.takenaddingthe tenant has a module of that id already
module.countaddingthe tenant has 20 modules already

Adding a module

A tenant's admins add a module in the administration by the address of its manifest. The server fetches the manifest, and the admins see its name, icon, places, areas and the permissions it asks for. Approving adds the module, with those permissions, to the tenant. A module is then on or off in the tenant, its worlds and its rooms, as a flag is (Tenancy), and on nowhere until an admin turns it on.

The server fetches each manifest again once a day. A new version that asks for no more than was granted replaces the old one. One that asks for more keeps the old version working until an admin approves the new one.

A module with a server side lists, in server, the partner API scopes and webhook topics it needs. Approving it creates an API key with those scopes and a webhook for those topics in the tenant, named after the module. The administration shows the key and the webhook's signing secret once, for the admin to hand to whoever runs the module's server, as for any partner integration. Removing the module revokes both.

The platform offers modules only where VWS_FEATURES names modules, which it does not by default (Configuration).

The administration's Modules page lists the tenant's modules: for each, its name, release, places and permissions, a newer version waiting for approval, and Remove. Add a module asks for the address, shows what the manifest asks for, and adds it. The endpoints are GET and POST /api/v1/admin/modules, POST /api/v1/admin/modules/preview, POST /api/v1/admin/modules/{module}/approve and DELETE /api/v1/admin/modules/{module}; a refused manifest answers 422 with its issues, in the shape of a refused map package. The settings of the tenant, each world and each room turn modules on, off or forbid them by their ids, under modules (Tenancy), and GET /api/v1/resolve lists the manifests of the modules a room turns on, which the client shows.

Map areas

A module may add kinds of map area. Each declares a type, a name, a JSON Schema of its settings and the page to open. In rooms where the module is on, the map editor offers the area kind among the behaviours as <module id>/<type>, with a form drawn from the schema. A player who enters such an area opens the module's page in the side panel, as a website area opens its page, and the page reads the settings from world.module.

The layout stores the behaviour with the module's id, its type and the settings. In a room where the module is off, the area does nothing. The editor checks the settings against the schema, but the server checks only that they are a JSON object of at most 16 KiB, since a module's schema can change.

Data

Modules keep their data on their own servers. In the room they may use room and player variables through the API, within their state permission.

Calendars and tasks

Calendar and task sync is part of the product, for Google and Microsoft accounts. The product only reads calendars: it never writes to them.

  • Connecting. From the account menu, a registered account connects a Google or Microsoft account through OAuth 2.0 with PKCE and offline access, reading calendars and tasks only. The product keeps the token in calendar_connection, a tenant-scoped table, sealed with VWS_ENCRYPTION_KEY (AES-256-GCM), one connection per account and provider, and the person can disconnect at any time, which deletes it. Google is also asked to revoke the token; Microsoft offers no revocation of one grant's tokens, which expire unused after 90 days.
  • Operators. Operators register one OAuth client with Google and one with Microsoft for the platform, through VWS_GOOGLE_CLIENT_ID and VWS_MICROSOFT_CLIENT_ID with their secrets (Configuration, Operations), whose redirect URI is the platform's public URL with /api/v1/calendars/callback. Tenants switch the feature with the flag calendars, on by default where the platform offers calendars (Tenancy).
  • In the room. A panel shows the person's meetings of the day and the tasks due today. A meeting whose location or description holds an address of one of the tenant's rooms offers Go to the room. Five minutes before a meeting, the person is reminded, as an event they can turn off (Identity).
  • Scheduled meetings. The product's scheduled meetings offer an .ics file that adds them to any calendar: members download it from the room's meeting list, guests from the page of the meeting's link, and every invitation email carries it.

Connecting

The platform has one OAuth client per provider and so one redirect URI, on its public URL, for every tenant. A connection therefore goes through four steps, so that a calendar is connected only to the account that asked for it:

  1. POST /api/v1/me/calendars with the provider and the path of the page to come back to keeps a grant in Redis for ten minutes (the tenant, the account, the provider, the PKCE verifier and the page) under a random state, and answers the provider's authorization address, where the browser goes.
  2. The provider sends the browser to GET /api/v1/calendars/callback on the public URL, which takes the grant once, exchanges the code with the verifier, reads the address of the account at the provider and keeps the sealed token in Redis for ten minutes under a random claim.
  3. The callback sends the browser back to the page on the tenant's host with the query parameter calendar, the claim, or calendarError, denied when the person refused and failed when the exchange failed.
  4. The client takes the claim out of the address and sends it with POST /api/v1/me/calendars/claim, with its own access token. Only the account that started the grant connects the calendar; any other account's claim is refused and spent, so that a link to a provider's consent page that someone else started connects nothing to their account.

GET /api/v1/me/calendars lists the providers the account may connect, none while the flag is off or for an account that is not registered, and its connections, which stay listed while the flag is off so that they can still be removed.

ProviderScopesReads
Googleopenid, email, calendar.events.readonly, tasks.readonlythe primary calendar's events (Calendar API, single events in order), the open tasks of every task list due on the day (Tasks API)
Microsoftopenid, email, offline_access, User.Read, Calendars.Read, Tasks.Readthe default calendar's view of the day in UTC, the open tasks of every To Do list due on the day (Graph); the common endpoints admit work, school and personal accounts

Google is asked for consent each time, as it hands out a refresh token only then. A token is refreshed when it expires and kept sealed again, with the new refresh token Microsoft hands out with each refresh. A connection whose provider refuses its refresh token (invalid_grant) turns expired and is no longer read until the person connects again, which renews it.

The day

GET /api/v1/me/agenda takes the person's day as the client sees it: its date and the instants it starts and ends in the person's time zone, which only the browser knows, 23 to 25 hours apart. It answers the meetings, all-day ones first and then by their start, without cancelled ones and ones the person declined, and the open tasks due on the date; a To Do due date written as the instant of midnight in the person's time zone counts for the day that instant falls in. A meeting whose location or description holds an address of one of the tenant's rooms, on one of the tenant's hosts, names the room. Connections that could not be read are listed apart, expired or active when the provider failed this time. An account reads its agenda ten times in a row, then once every 30 seconds, which spares the platform's quotas at the providers; the client reads it every five minutes.

Reminders

The client reminds the person five minutes before each meeting that is not all-day, also of a meeting it learns of when it is closer than that: with a browser notification while the page is in the background, and with a reminder on the page, with Go to the room where the meeting names one, until the person dismisses it. Neither comes while the person turned the event meeting_reminder off or does not want to be disturbed. Reminders come only while the app is open, as notifications do.

Development

A development platform offers the development calendar where VWS_ENCRYPTION_KEY is set, whether or not it has OAuth clients. It grants at once, without leaving the platform, as calendar@development.test, and each of its days holds an all-day "Office day", a "Stand-up" in the seeded room /dev/office that starts five minutes and twenty seconds after the connection, so that its reminder comes twenty seconds after connecting, a "Planning" meeting at two in the afternoon, and two tasks.

Calendar files

A meeting's calendar file is an iCalendar file (RFC 5545, METHOD:PUBLISH) with the meeting's title, start, end and status, and its address as LOCATION and URL: the room's for members, from GET /api/v1/meetings/{meeting}/calendar, and the meeting's link for guests, from GET /api/v1/invites/{token}/calendar and as the attachment meeting.ics of every invitation email. Its description says how to join, in the language of the request or of the email, with the outside tool's link where the meeting has one. The files of one meeting share its UID, so a calendar that imports two keeps one event. The page of a meeting's link shows the meeting from the link's creation, with the time the link opens, so that a guest can add it to their calendar before the meeting.

Building it

  • Modules are built: the manifest's schema and checks, the administration of modules and their settings, permissions in the scripting host, the module places of the client, module areas in the map format and the editor, and the API key and webhook of a module's server side. While developing, a module may be served over http from the developer's machine, whose manifest a development platform fetches, whose pages it frames and whose icons it shows.
  • Calendars and tasks are built: the OAuth connections for Google and Microsoft with their sealed tokens, the development calendar, the day's meetings and tasks with the rooms they name, reminders, and the calendar files of scheduled meetings, which invitation emails carry.

Found a mistake? Tell us.