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
scriptlists the map's scripts, one per line: a.jsor.mjsfile 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
allowApimay use the API too: awebsiteorembedarea of the layout (Map format), or a panel, overlay, modal or menu page a script opened withallowApi: 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
scriptingfeature (Tenancy), the client loads no map script and answers every call with the errorscripting_off. - Typings. Authors download
https://<host>/scripting/v1/world.d.tsnext to their scripts and reference it with/// <reference path="./world.d.ts" />, or list it intsconfig.json.scripting/example.tsis 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.
| Direction | Shape | Use |
|---|---|---|
| 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.sourceis the window of an iframe it registered for a map script or anallowApipage. 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 (
Jsonin the typings), except audio samples, which areFloat32Array; variables are plain objects, notMaps. - Timeouts. A query the client does not answer within 15 seconds fails with
timeout, exceptplayer.moveTo, which waits for the walk, and audio appends, which wait for room in the buffer. - Errors.
codeis one ofscripting_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,timeoutandunavailable. The page's promise rejects with anErrorwhose message is the answer'smessageand whosecodeis 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),mapUrlandentry, the entry point of the page's address:start,moveToandmoveToUser.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 propertyname, 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 theirdescriptionand whether they aresearchable;areas.get(name)one of them;areas.create({name, x, y, width, height})an area only this player's client knows.areas.onEnter(name)andareas.onLeave(name)follow the player across an area of either kind. AnAreamoves or resizes withmove(...), overrides a property for this player withsetProperty(name, value)and, when the script created it, goes withremove().embeds.create({...}),get(name)andlist()manage pages shown in a rectangle of the map for this player, as theembedproperty does for everyone; anEmbedchanges withupdate(...)and goes withremove().
world.player
id(the player's number in the room),userId(its account),name,language(the language the client speaks to the player, Languages),tagsandkind(anonymous,guestorregistered).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, andteleport(x, y).setOutline({red, green, blue})andclearOutline()ring the player's avatar in a colour every player sees.getAvatarPicture()answers a PNG of the avatar as a data URL.setStatus(status)choosesonline,busy,doNotDisturborbackInAMoment, as the account menu does.say(text, {kind, durationMs})shows a speech or thought bubble, as the say line does.statekeeps the player's variables:save(key, value, {public, persist, ttlSeconds, scope}),load,hasandonChange. A variable is private unlesspublic, persisted by default, kept for the world unlessscopeisroom, and kept only for its session when not persisted;ttlSecondsneedspersist.follow.lead()asks the player's bubble to follow it,stopLeading()ends it, andonFollowed()andonUnfollowed()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, andclose()removes it.menu.add(label, {onClick} or {page})adds an entry to the account menu, with akeytogetit again; aMenuItemcanopen()andremove().actionMessages.show({message, kind, onAction})shows a prompt above the bottom bar that Space or a tap accepts, untilremove().panels.open({url, allowApi, policy, width, closable})opens a page in the side panel thatwebsiteareas use, answering aPanelthat canclose();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; anOverlaychanges withupdate(...)and goes withclose().modal.open({url, title, position, fullScreen, closable, allowApi, policy}, onClose)shows a page in a dialog, andmodal.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, untilstop().
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.setOutlineandclearOutline, as profile updates;player.teleport, as a move withteleport;player.follow, as follow actions;chat.sendwith the conversation scope andchat.startTyping, as chat messages;spaces.joinand its members, as space messages, with media for streaming;meetingsaudio, as a track in the meeting's media room.
Found a mistake? Tell us.