This page contains detailed documentation to help you use the Spaces SDK.
To start using this SDK, you will need the following:
- An Ably account
- You can sign up to the generous free tier.
- An Ably API key
- Use the default or create a new API key in an app within your Ably account dashboard.
- Make sure your API key has the following capabilities:
publish
,subscribe
,presence
andhistory
.
Spaces is built on top of the Ably JavaScript SDK. Although the SDK supports Node.js and other JavaScript environments, at the time of writing our main target is an ES6 compatible browser environment.
You'll need to install both the ably client and the spaces client:
npm install ably @ably/spaces
You can also use Spaces with a CDN like unpkg:
<script src="https://cdn.ably.com/lib/ably.min-1.js"></script>
<script src="https://unpkg.com/@ably/[email protected]/dist/iife/index.bundle.js"></script>
Note
If you do this, then replace the call to
new Realtime.Promise
in the next section withnew Ably.Realtime.Promise
.
Spaces use an Ably promise-based realtime client. You can pass an Ably client directly to the spaces constructor.
The Ably client instantiation accepts client options. You will need at minimum an Ably API key and a clientId. A clientId represents an identity of an connection. In most cases this will something like the id of a user:
import { Realtime } from 'ably/promise';
import Spaces from '@ably/spaces';
const client = new Realtime.Promise({ key: "<API-key>", clientId: "<client-ID>" });
const spaces = new Spaces(client);
You can access the Ably client via spaces.ably
.
To learn more about authenticating with ably, see our authentication documentation.
A space is the virtual area of an application you want users to collaborate in, such as a web page, or slideshow. A space
is uniquely identified by its name. A space is created, or an existing space retrieved from the spaces
collection by calling the get()
method. You can only connect to one space in a single operation. The following is an example of creating a space called "demonSlideshow":
const space = await spaces.get('demoSlideshow');
A set of spaceOptions
can be passed to space when creating or retrieving it. See the class definitions for details on what options are available.
The following is an example of setting offlineTimeout
to 3 minutes and a paginationLimit
of 10:
const space = await spaces.get('demoSlideshow', { offlineTimeout: 180_000, cursors: { paginationLimit: 10 } });
You can subscribe to events in a space:
space.subscribe('update', (spaceState) => {
console.log(spaceState);
});
This gets triggered on member and location events.
Similarly you can unsubscribe:
space.unsubscribe();
To become a member of a space (and use the other APIs, like location or cursors) a client needs to enter a space.
await space.enter();
This method can take an optional object called profileData
so that users can include convenient metadata to update an avatar stack, such as a username and profile picture.
await space.enter({
username: 'Claire Lemons',
avatar: 'https://slides-internal.com/users/clemons.png',
});
A leave event is sent when a user leaves a space. This can occur for one of the following reasons:
space.leave()
is called explicitly- The user closes the tab
- The user is abruptly disconnected from the internet for longer than 2 minutes
A leave event does not remove the member immediately from space.members
. Instead, they are removed after a timeout which is configurable by the offlineTimeout
option. This allows the UI to display an intermediate state before disconnection/reconnection.
As with enter
, you can update the profileData
on leave:
await space.leave({
username: 'Claire Lemons',
avatar: 'https://slides-internal.com/users/inactive.png',
});
To update profileData
after entering the space, use the updateProfileData()
method. Pass new profileData
or a function to base the new profileData
of the existing value:
await space.updateProfileData((oldProfileData) => {
return {
...oldProfileData,
username: 'Clara Lemons'
}
});
When you enter a space, you become a member
. On the client, your own membership is to referred to as self
. You can get your self
by calling space.members.getSelf()
. To get all the members (including self), call space.members.getAll()
. These methods will return (respectively an object and array of):
{
"clientId": "clemons#142",
"connectionId": "hd9743gjDc",
"isConnected": true,
"lastEvent": {
"name": "enter",
"timestamp": 1677595689759
},
"location": null,
"profileData": {
"username": "Claire Lemons",
"avatar": "https://slides-internal.com/users/clemons.png"
}
}
See SpaceMember for details on properties.
Subscribe to either all the member events or specifically to enter
, leave
, remove
or updateProfile
events related to members in a space.
To listen to all events pass either no event name or update
:
space.members.subscribe((memberUpdate) => {
console.log(memberUpdate);
});
space.members.subscribe('update', (memberUpdate) => {
console.log(memberUpdate);
});
Emitted when a member enters a space. Called with the member entering the space.
space.members.subscribe('enter', (memberJoined) => {
console.log(memberJoined);
});
Emitted when a member leaves a space. Called with the member leaving the space.
space.members.subscribe('leave', (memberLeft) => {
console.log(memberLeft);
});
Emitted when a member is removed from a space. Called with the member removed from the space.
space.members.subscribe('remove', (memberRemoved) => {
console.log(memberRemoved);
});
Emitted when a member updates their profileData
via space.updateProfileData()
:
space.members.subscribe('updateProfile', (memberProfileUpdated) => {
console.log(memberProfileUpdated);
});
To stop listening to member events, users can call the space.members.unsubscribe()
method. See Event emitters for options and usage.
Each member can set a location for themselves:
space.locations.set({ slide: '3', component: 'slide-title' });
A location does not have a prescribed shape. In your UI it can represent a single location (an id of a field in form), multiple locations (id's of multiple cells in a spreadsheet) or a hierarchy (a field in one of the multiple forms visible on screen).
The location property will be set on the member.
A location event will be emitted when a member updates their location:
space.locations.subscribe('update', (locationUpdate) => {
console.log(locationUpdate);
});
When a member leaves, their location is set to null
.
This event will include the member affected by the change, as well as their previous and current locations:
{
"member": {
"clientId": "clemons#142",
"connectionId": "hd9743gjDc",
"isConnected": true,
"profileData": {
"username": "Claire Lemons",
"avatar": "https://slides-internal.com/users/clemons.png"
},
"location": {
"slide": "3",
"component": "slide-title"
},
"lastEvent": {
"name": "update",
"timestamp": 1
}
},
"previousLocation": {
"slide": "2",
"component": null
},
"currentLocation": {
"slide": "3",
"component": "slide-title"
}
}
A common feature of collaborative apps is to show where a users cursors is positioned in realtime. It's easy to accomplish this with the cursors
API.
The most common use case is to show the current mouse pointer position.
To start listening to cursor events, use the subscribe()
method:
space.cursors.subscribe('update', (cursorUpdate) => {
console.log(cursorUpdate);
});
The listener will be called with a CursorUpdate
:
{
"connectionId": "hd9743gjDc",
"clientId": "clemons#142",
"position": { "x": 864, "y": 32 },
"data": { "color": "red" }
}
To set the position of a cursor and emit a CursorUpdate
, first enter the space if you haven't already:
space.enter();
Then call set
:
window.addEventListener('mousemove', ({ clientX, clientY }) => {
space.cursors.set({ position: { x: clientX, y: clientY } });
});
A CursorUpdate
is an object with 2 properties. position
is an object with 2 required properties, x
and y
. These represent the position of the cursor on a 2D plane. A second optional property, data
can also be passed. This is an object of any shape and is meant for data associated with the cursor movement (like drag or hover calculation results):
window.addEventListener('mousemove', ({ clientX, clientY }) => {
space.cursors.set({ position: { x: clientX, y: clientY }, data: { color: 'red' } });
});
To retrieve the initial position and data of all cursors within a space, you can use the space.cursors.getAll()
method. This returns an object keyed by connectionId
. The value is the last cursorUpdate
set by the given connectionId
.
Example of calling getAll()
to return all cursor positions:
const lastPositions = await space.cursors.getAll();
{
"hd9743gjDc": {
"connectionId": "hd9743gjDc",
"clientId": "clemons#142",
"position": {
"x": 864,
"y": 32
},
"data": {
"color": "red"
}
}
}
space
, members
, cursors
and locations
are event emitters. Event emitters provide subscribe()
and unsubscribe()
methods to attach/detach event listeners. Both methods support overloaded versions, described below.
Calling subscribe()
with a single function argument will subscribe to all events on that emitter.
space.members.subscribe();
Calling subscribe()
with a named event and a function argument will subscribe to that event only.
space.members.subscribe('enter', () => {});
Calling subscribe()
with an array of named events and a function argument will subscribe to those events.
space.members.subscribe(['enter', 'leave'], () => {});
Calling unsubscribe()
with no arguments will remove all registered listeners.
space.members.unsubscribe();
Calling unsubscribe()
with a single named event will remove all listeners registered for that event.
space.members.unsubscribe('enter');
Calling unsubscribe()
with an array of named events will remove all listeners registered for those events.
space.members.unsubscribe(['enter', 'leave']);
Calling unsubscribe()
and adding a listener function as the second argument to both of the above will remove only that listener.
const listener = () => {};
space.members.unsubscribe('update', listener);
space.members.unsubscribe(['update'], listener);
As with the native DOM API, this only works if the listener is the same reference as the one passed to subscribe()
.