Skip to content

Developers

Scripting API

Map scripts and the pages a map allows talk to the room through one global, world, version 1. This document is its specification. The typings in scripting/world.d.ts pin every member; each host serves them at /scripting/v1/world.d.ts, and the client hosts the API. The API is our own.

Loading

  • Map scripts. The Tiled map property script lists the map's scripts, one per line: a .js or .mjs file of the map package, named relative to the Tiled document, or an https URL; the upload refuses more than 8 and anything else (Map format). The client loads each in a hidden iframe of its own once the player is in the room, and unloads them when the player leaves it.
  • Pages. A page opened with allowApi may use the API too: a website or embed area of the layout (Map format), or a panel, overlay, modal or menu page a script opened with allowApi: true. Such a page loads the API itself with <script src="https://<host>/scripting/v1/world.js"></script>, from the host of the room.
  • The flag. Where a room does not offer the scripting feature (Tenancy), the client loads no map script and answers every call with the error scripting_off.
  • Typings. Authors download https://<host>/scripting/v1/world.d.ts next to their scripts and reference it with /// <reference path="./world.d.ts" />, or list it in tsconfig.json. scripting/example.ts is a script that uses most of the API.

Sandbox and messages

A map script's iframe has sandbox="allow-scripts" and loads a page the host serves at /scripting/v1/frame.html, which loads /scripting/v1/world.js and then the script as a module. The frame page carries a content security policy of its own that admits the API and the script's origin, so that the room's page keeps its strict one and no external script runs under it. The iframe has an opaque origin: it reads nothing of the room's page, cannot navigate it and opens nothing by itself, and everything it does goes through the API. A script of the package is served with the map's other files, under a policy that sandboxes it, so that opening it in a tab runs nothing on the room's origin. A page with allowApi keeps the sandbox and the allow policy its opener gave it.

Page addresses a script names, such as a modal's or a panel's, are https URLs or relative to the script's own address.

The API travels as postMessage between the iframe and the client. Every message carries protocol: "world/1", and the client ignores any other.

DirectionShapeUse
page to client{protocol, kind: "command", type, data}an action with no answer
page to client{protocol, kind: "query", id, type, data}an action with an answer; id counts up from 1 per page
client to page{protocol, kind: "answer", id, data} or {protocol, kind: "answer", id, error: {code, message}}the answer to a query
page to client{protocol, kind: "subscribe", id, type, data} and {protocol, kind: "unsubscribe", id}opens or ends a stream
client to page{protocol, kind: "event", id, data}a value of the stream id
  • Who may call. The client accepts a message only when its event.source is the window of an iframe it registered for a map script or an allowApi page. Sandboxed pages have an opaque origin, so the client does not check origins and posts with target origin *; it therefore never sends a page anything the player could not see.
  • Values. Data is plain JSON (Json in the typings), except audio samples, which are Float32Array; variables are plain objects, not Maps.
  • Timeouts. A query the client does not answer within 15 seconds fails with timeout, except player.moveTo, which waits for the walk, and audio appends, which wait for room in the buffer.
  • Errors. code is one of scripting_off, not_ready (before init), invalid (a malformed or out-of-range call), forbidden (the player may not, such as an undeclared variable or a missing tag), not_found, timeout and unavailable. The page's promise rejects with an Error whose message is the answer's message and whose code is its code.
  • Streams. Values go only to the page that subscribed, never to every page. A page's subscriptions, popups, menu items, toolbar buttons, disabled controls, banners and local areas go away when the page closes.

Initialisation

world.onInit() resolves once the player is in the room and the client has sent the page the state it starts from: the room's id, worldId, path and mapUrl, its entry point, the player's id, userId, name, language, tags and kind, the room variables the player may read and the player's own variables. Reading world.room, world.player, world.players or world.state before that throws not_ready. A page that loads after the player arrived resolves at once.

Events

Members named on… return a Stream: subscribe(next) starts it and returns a Subscription whose unsubscribe() ends it. A stream of a state, such as room.areas.onEnter, first delivers the current state with initial set, so that a script need not ask separately.

Versioning

world.version is 1. followed by the client's minor version. Version 1 grows only by additions: a new member, a new optional field, a new event. A change that would break a script goes into version 2, served at /scripting/v2/ beside version 1, and a map chooses it by loading that path. A script detects a member it needs by checking that it exists.

Namespaces

world.nav

openTab(url) opens a browser tab; goToPage(url) leaves for another page; goToRoom(room, {start}) walks the player into another room of the world without a page load, by slug or /world/room, at a start area; signIn() starts a sign-in with the tenant's providers (Identity).

world.room

  • id, worldId, path (/world/room), mapUrl and entry, the entry point of the page's address: start, moveTo and moveToUser.
  • getMap() answers the Tiled map as the package holds it.
  • layer(name).show() and .hide() show or hide a layer, a group with its children, and its collisions, for this player.
  • setTiles([{x, y, layer, tile}]) places tiles by id or by the Tiled tile property name, or clears cells, for this player; loadTileset(url) adds a tileset and answers its first id.
  • areas.list() answers the areas of the layout, with their description and whether they are searchable; areas.get(name) one of them; areas.create({name, x, y, width, height}) an area only this player's client knows. areas.onEnter(name) and areas.onLeave(name) follow the player across an area of either kind. An Area moves or resizes with move(...), overrides a property for this player with setProperty(name, value) and, when the script created it, goes with remove().
  • embeds.create({...}), get(name) and list() manage pages shown in a rectangle of the map for this player, as the embed property does for everyone; an Embed changes with update(...) and goes with remove().

world.player

  • id (the player's number in the room), userId (its account), name, language (the language the client speaks to the player, Languages), tags and kind (anonymous, guest or registered).
  • getPosition(), onMove() (on every stop, turn and about every 200 ms while walking), moveTo(x, y, {running}), which resolves with where the walk ended and whether something cancelled it, and teleport(x, y).
  • setOutline({red, green, blue}) and clearOutline() ring the player's avatar in a colour every player sees.
  • getAvatarPicture() answers a PNG of the avatar as a data URL.
  • setStatus(status) chooses online, busy, doNotDisturb or backInAMoment, as the account menu does.
  • say(text, {kind, durationMs}) shows a speech or thought bubble, as the say line does.
  • state keeps the player's variables: save(key, value, {public, persist, ttlSeconds, scope}), load, has and onChange. A variable is private unless public, persisted by default, kept for the world unless scope is room, and kept only for its session when not persisted; ttlSeconds needs persist.
  • follow.lead() asks the player's bubble to follow it, stopLeading() ends it, and onFollowed() and onUnfollowed() tell of followers.

world.players

track({movement}) starts the list of the other players the client knows, and with movement their moves; the streams need it. list(), get(id), onEnter(), onLeave(), onMove(), onClick() (the player clicked another's avatar or list entry) and onVariableChange(key) follow them. A RemotePlayer has id, userId, name, status, position and the public variables in state; sendEvent(name, data) sends it an event that its scripts hear through world.events, and addAction(label, onClick) adds an entry to its menu until the returned action's remove(). Other players' tags stay private.

world.state

The room's variables, which the map declares (Map format): save(key, value), load(key), has(key) and onChange(key). Saving an undeclared variable fails with invalid, one whose writeTags the player lacks with forbidden, and saving the value a variable holds changes nothing. The server keeps and spreads them.

world.chat

open() and close() the chat panel; send(text, {scope: "local", author}) writes a line only this player sees, and send(text, {scope: "conversation"}) writes to the player's conversation as the player; startTyping() and stopTyping() drive the typing line; onMessage() hears the conversation's messages, with the author's room number and name.

world.ui

  • popups.open({area, message, buttons}) shows a popup by an area of the map; only the opening page hears its buttons, and close() removes it.
  • menu.add(label, {onClick} or {page}) adds an entry to the account menu, with a key to get it again; a MenuItem can open() and remove().
  • actionMessages.show({message, kind, onAction}) shows a prompt above the bottom bar that Space or a tap accepts, until remove().
  • panels.open({url, allowApi, policy, width, closable}) opens a page in the side panel that website areas use, answering a Panel that can close(); panels.list() lists them.
  • overlays.open({url, position, size, margin, visible, allowApi, policy}) floats a page over the room, positioned by edges and sized in CSS lengths; an Overlay changes with update(...) and goes with close().
  • modal.open({url, title, position, fullScreen, closable, allowApi, policy}, onClose) shows a page in a dialog, and modal.close() closes it.
  • toolbar.add({id, label, tooltip, icon, location}, onClick) adds a button to the bottom bar or to its apps menu; toolbar.remove(id) takes it away. Colours come from the tenant's theme.
  • banner.show({id, text, link, closable, durationMs}) shows a banner, two minutes unless told otherwise; banner.close() closes it.
  • videos.play(url, {name, loop}) plays a video as a tile of the player's media, until stop().

world.controls

disable(...controls) and restore(...controls) hold controls off: movement, microphone, camera, screenShare, meetings (the player joins no bubble), editor, wheelZoom, rightClick, invite and roomList. A control stays off while any page holds it off, and a closing page lets go of what it held. turnOff("microphone") and turnOff("camera") switch a device off once, as the player could.

world.sound

load(url) answers a Sound that can play({volume, loop, rate, delayMs, seekMs}) and stop(), for this player.

world.camera

set({x, y, width, height, lock, smooth, durationMs}) points the camera at a rectangle, locked there or until the player walks; follow({smooth, durationMs}) gives it back to the player; onUpdate() follows what it shows.

world.events

broadcast(name, data) sends an event to every script of the room, the sender's included; on(name) hears them, with the sender's room number, which the partner API's events lack (Partner API). The server spreads them as script events and limits how often a player broadcasts.

world.meetings

onJoin() tells of every meeting the player enters, a room's broadcast only while it is live, as every player of the room is a member of its space: a Meeting with its id, kind (bubble, meeting, broadcast, speakerZone or chatArea), participants, onParticipantJoin(), onParticipantLeave() and onLeave(). playSound(url) plays a sound to the meeting's participants; startAudioStream(sampleRate) publishes samples the script appends as an audio track of the meeting's media room, and listenToAudioStream(sampleRate) hands the script the meeting's sound.

world.spaces

join(name, {filter, metadata}) joins a space of the script's own (SPACE_KIND_CUSTOM), listing everyone or only broadcasters. A space belongs to the world: the players of every room of the world that join a space of one name meet in it, and a SpaceMember names its roomId, the room it joined from and in which its playerId holds, so that a script tells its own player and the members of other rooms apart; roomId is absent for a member that joined from no room. A script that wants a space per room puts world.room.id in the name. A Space has its members, onMemberJoin(), onMemberLeave(), onMetadataChange(), setMetadata(fields), startStreaming() and stopStreaming() (the player's media as a broadcaster) and leave(). Joins of one space from several pages of the player count as one, and its last leave leaves it. The player keeps its spaces across a new realtime session and a space the server destroys: the client joins them again with the filter and the metadata of the first join, once the new session is in the room or 5 seconds after the destruction, and the Space hears its members leave and join again. A space a moderator removed the player from stays left.

world.module

On a page of a module (Extensions), world.module names the module (id) and, on the page of a module's map area, the area's settings; on the pages of maps it is undefined. A module's page may make only the calls its module's permissions grant, and any other is refused with forbidden, whatever the room's scripting flag: room reads the room, its areas and embeds and its players; player moves the player, speaks for it and sets its status and outline; state reads and writes room and player variables; chat, ui, controls, nav and events grant their namespaces, players.action going with ui and players.sendEvent with events; sound grants sound and camera; meetings grants meetings and spaces. No permission lets a module change the map: its tiles, layers, areas and embeds.

Server linkage

Most of the API acts in the client. These members reach the server, over the realtime protocol:

  • room and player variables (state, player.state, players.onVariableChange), as the protocol's variables, and script events (events, RemotePlayer.sendEvent), as the protocol's script events, with the partner API's (Partner API);
  • player.setStatus, player.say, player.setOutline and clearOutline, as profile updates;
  • player.teleport, as a move with teleport;
  • player.follow, as follow actions;
  • chat.send with the conversation scope and chat.startTyping, as chat messages;
  • spaces.join and its members, as space messages, with media for streaming;
  • meetings audio, as a track in the meeting's media room.

Found a mistake? Tell us.