Gathering detailed insights and metrics for @ably/spaces
Gathering detailed insights and metrics for @ably/spaces
Gathering detailed insights and metrics for @ably/spaces
Gathering detailed insights and metrics for @ably/spaces
The Spaces SDK enables you build collaborative spaces for your application and enable features like Avatar stack, Live cursors, Member location, Component locking and more.
npm install @ably/spaces
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
44 Stars
418 Commits
8 Forks
13 Watching
35 Branches
24 Contributors
Updated on 26 Nov 2024
TypeScript (98.7%)
JavaScript (0.87%)
HTML (0.44%)
Cumulative downloads
Total Downloads
Last day
-36%
581
Compared to previous day
Last week
-28.2%
3,489
Compared to previous week
Last month
19.9%
18,523
Compared to previous month
Last year
1,566.5%
97,455
Compared to previous year
1
25
The Spaces SDK contains a purpose built set of APIs that help you build collaborative environments for your apps to quickly enable remote team collaboration. Try out a live demo of a slideshow application for an example of realtime collaboration powered by the Spaces SDK.
Ably is the scalable and five nines reliable middleware, used by developers at over 500 companies worldwide, to quickly build realtime applications - from collaborative experiences and live chat, to data broadcast and notifications. Supported by its globally distributed infrastructure, Ably offers 25 SDKs for various programming languages and platforms, as well as partner integrations with technologies including Datadog, Kafka, and Vercel, that accelerate the delivery of realtime experiences.
Rather than having to coordinate resources on calls, or send documents and spreadsheets back and forth using a combination of tools, having in-app realtime collaboration features has proven to boost productivity in remote workplaces. Such features enable end users to have contextual awareness of other users within an application. This means knowing:
One of the most important aspects of collaboration is knowing who else you're working with. The most common way to display this is using an "Avatar Stack" to show who else is currently online, and those that have recently gone offline.
Knowing where each user is within an application helps you understand their intentions without having to explicitly ask them. For example, seeing that a colleague is currently viewing slide 2 of a presentation deck means that you can carry out your own edits to slide 3 without interfering with their work. Displaying the locations of your users can be achieved by highlighting the UI element they have selected, displaying a miniature avatar stack on the slide they are viewing, or showing the live location of their cursors. In Spaces, we call this "Member Location".
Changes to the app state made by users not only need to be synced with your backend for validation and long term storage, but also be immediately reflected in the UI so that users are always viewing the latest information. For example, in a spreadsheet application, if one user has entered a value in a cell, all other users need to see that change instantly. Pub/Sub Channels help flexibly broadcast live updates in a collaborative space.
The Spaces SDK is currently under development. If you are interested in being an early adopter and providing feedback then you can sign up for early access and are welcome to provide us with feedback.
The next section gives you an overview of how to use the SDK. Alternatively, you can jump to:
To start using this SDK, you will need the following:
publish
, subscribe
, presence
and history
.Install the Ably JavaScript SDK and the Spaces SDK:
1npm install ably @ably/spaces
To instantiate the Spaces SDK, create an Ably client and pass it into the Spaces constructor:
1import Spaces from '@ably/spaces'; 2import { Realtime } from 'ably'; 3 4const client = new Realtime({ key: "<API-key>", clientId: "<client-ID>" }); 5const spaces = new Spaces(client);
You can use basic authentication i.e. the API Key directly for testing purposes, however it is strongly recommended that you use token authentication in production environments.
To use Spaces you must also set a clientId
so that clients are identifiable. If you are prototyping, you can use a package like nanoid to generate an ID.
You can also use Spaces with a CDN, such as unpkg:
1<script src="https://cdn.ably.com/lib/ably.min-2.js"></script> 2<script src="https://cdn.ably.com/spaces/0.4.0/iife/index.bundle.js"></script>
After this, instantiate the SDK in the same way as in the NPM option above:
1const client = new Ably.Realtime({ key: "<API-key>", clientId: "<client-ID>" });
2const spaces = new Spaces(client);
A set of React Hooks are available which make it seamless to use Spaces in any React application. See the React Hooks documentation for further details.
A space is the virtual area of your application where you want to enable synchronous collaboration. A space can be anything from a web page, a sheet within a spreadsheet, an individual slide in a slideshow, or the slideshow itself. A space has a participant state containing online and recently left members, their profile details, their locations and any locks they have acquired for the UI components.
Create a space and subscribe to any updates to the participant state.
1// Create a new space 2const space = await spaces.get('demoSlideshow'); 3 4// Subscribe to space state events 5space.subscribe('update', (spaceState) => { 6 console.log(spaceState.members); 7}); 8 9// Enter a space, publishing an update event, including optional profile data 10await space.enter({ 11 username: 'Claire Lemons', 12 avatar: 'https://slides-internal.com/users/clemons.png', 13});
The following is an example event payload received by subscribers when a user enters a space:
1[ 2 { 3 "clientId": "clemons#142", 4 "connectionId": "hd9743gjDc", 5 "isConnected": true, 6 "lastEvent": { 7 "name": "enter", 8 "timestamp": 1677595689759 9 }, 10 "location": null, 11 "profileData": { 12 "username": "Claire Lemons", 13 "avatar": "https://slides-internal.com/users/clemons.png" 14 } 15 } 16]
The members
namespace contains methods dedicated to building avatar stacks. Subscribe to members entering, leaving, being removed from the Space (after a timeout) or updating their profile information.
1// Subscribe to all member events in a space 2space.members.subscribe((memberUpdate) => { 3 console.log(memberUpdate); 4}); 5 6// Subscribe to member enter events only 7space.members.subscribe('enter', (memberJoined) => { 8 console.log(memberJoined); 9}); 10 11// Subscribe to member leave events only 12space.members.subscribe('leave', (memberLeft) => { 13 console.log(memberLeft); 14}); 15 16// Subscribe to member remove events only 17space.members.subscribe('remove', (memberRemoved) => { 18 console.log(memberRemoved); 19}); 20 21// Subscribe to profile updates on members only 22space.members.subscribe('updateProfile', (memberProfileUpdated) => { 23 console.log(memberProfileUpdated); 24}); 25 26// Subscribe to all updates to members 27space.members.subscribe('update', (memberUpdate) => { 28 console.log(memberUpdate); 29});
The following is an example event payload received by subscribers when member updates occur in a space:
1{ 2 "clientId": "clemons#142", 3 "connectionId": "hd9743gjDc", 4 "isConnected": true, 5 "lastEvent": { 6 "name": "enter", 7 "timestamp": 1677595689759 8 }, 9 "location": null, 10 "profileData": { 11 "username": "Claire Lemons", 12 "avatar": "https://slides-internal.com/users/clemons.png" 13 } 14}
Space members has methods to get the current snapshot of member state:
1// Get all members in a space 2const allMembers = await space.members.getAll(); 3 4// Get your own member object 5const myMemberInfo = await space.members.getSelf(); 6 7// Get everyone else's member object but yourself 8const othersMemberInfo = await space.members.getOthers();
The locations
namespace contains methods dedicated to building member locations, enabling you to track where users are within an application. A location could be a form field, multiple cells in a spreadsheet or a slide in a slide deck editor.
1// You need to enter a space before publishing your location 2await space.enter({ 3 username: 'Claire Lemons', 4 avatar: 'https://slides-internal.com/users/clemons.png', 5}); 6 7// Publish your location based on the UI element selected 8await space.locations.set({ slide: '3', component: 'slide-title' }); 9 10// Subscribe to location events from all members in a space 11space.locations.subscribe('update', (locationUpdate) => { 12 console.log(locationUpdate); 13}); 14
The following is an example event payload received by subscribers when a member changes location:
1{ 2 "member": { 3 "clientId": "clemons#142", 4 "connectionId": "hd9743gjDc", 5 "isConnected": true, 6 "profileData": { 7 "username": "Claire Lemons", 8 "avatar": "https://slides-internal.com/users/clemons.png" 9 }, 10 "location": { 11 "slide": "3", 12 "component": "slide-title" 13 }, 14 "lastEvent": { 15 "name": "update", 16 "timestamp": 1 17 } 18 }, 19 "previousLocation": { 20 "slide": "2", 21 "component": null 22 }, 23 "currentLocation": { 24 "slide": "3", 25 "component": "slide-title" 26 } 27}
Member locations has methods to get the current snapshot of member state:
1// Get a snapshot of all the member locations 2const allLocations = await space.locations.getAll(); 3 4// Get a snapshot of my location 5const myLocation = await space.locations.getSelf(); 6 7// Get a snapshot of everyone else's locations 8const othersLocations = await space.locations.getOthers();
The cursors
namespace contains methods dedicated to building live cursors, allowing you to track a member's pointer position updates across an application. Events can also include associated data, such as pointer attributes and the IDs of associated UI elements:
1// You need to enter a space before publishing your cursor updates 2await space.enter({ 3 username: 'Claire Lemons', 4 avatar: 'https://slides-internal.com/users/clemons.png', 5}); 6 7// Subscribe to events published on "mousemove" by all members 8space.cursors.subscribe('update', (cursorUpdate) => { 9 console.log(cursorUpdate); 10}); 11 12// Publish a your cursor position on "mousemove" including optional data 13window.addEventListener('mousemove', ({ clientX, clientY }) => { 14 space.cursors.set({ position: { x: clientX, y: clientY }, data: { color: 'red' } }); 15});
The following is an example event payload received by subscribers when a member moves their cursor:
1{ 2 "connectionId": "hd9743gjDc", 3 "clientId": "clemons#142", 4 "position": { "x": 864, "y": 32 }, 5 "data": { "color": "red" } 6}
Member cursors has methods to get the current snapshot of member state:
1// Get a snapshot of all the cursors 2const allCursors = await space.cursors.getAll(); 3 4// Get a snapshot of my cursor 5const myCursor = await space.cursors.getSelf(); 6 7// Get a snapshot of everyone else's cursors 8const othersCursors = await space.cursors.getOthers();
Use the Component Locking API to lock stateful components whilst being edited by members to reduce the chances of conflicting changes being made.
Locks are identified using a unique string, and the Spaces SDK maintains that at most one member holds a lock with a given string at any given time.
The Component Locking API supports four operations: Query, Acquire, Release, and Subscribe.
space.locks.get
is used to query whether a lock identifier is currently locked and by whom. It returns a Lock
type which has the following fields:
1type Lock = { 2 id: string; 3 status: LockStatus; 4 member: SpaceMember; 5 timestamp: number; 6 attributes?: LockAttributes; 7 reason?: Types.ErrorInfo; 8};
For example:
1// check if the id is locked 2const isLocked = space.locks.get(id) !== undefined; 3 4// check which member has the lock 5const { member } = space.locks.get(id); 6 7// check the lock attributes assigned by the member holding the lock 8const { attributes } = space.locks.get(id); 9const value = attributes.get(key);
space.locks.getAll
returns all lock identifiers which are currently locked as an array of Lock
:
1const allLocks = space.locks.getAll(); 2 3for (const lock of allLocks) { 4 // ... 5}
space.locks.acquire
sends a request to acquire a lock using presence.
It returns a Promise which resolves once the presence request has been sent.
1const req = await space.locks.acquire(id); 2 3// or with some attributes 4const attributes = new Map(); 5attributes.set('key', 'value'); 6const req = await space.locks.acquire(id, { attributes });
It throws an error if a lock request already exists for the given identifier with a status of pending
or locked
.
space.locks.release
releases a previously requested lock by removing it from presence.
It returns a Promise which resolves once the presence request has been sent.
1await space.locks.release(id);
space.locks.subscribe
subscribes to changes in lock status across all members.
The callback is called with a value of type Lock
.
1space.locks.subscribe('update', (lock) => { 2 // lock.member is the requesting member 3 // lock.request is the request made by the member 4}); 5 6// or with destructuring: 7space.locks.subscribe('update', ({ member, request }) => { 8 // request.status is the status of the request, one of PENDING, LOCKED, or UNLOCKED 9 // request.reason is an ErrorInfo if the status is UNLOCKED 10});
Such changes occur when:
pending
request transitions to locked
because the requesting member now holds the lockpending
request transitions to unlocked
because the requesting member does not hold the lock since another member already doeslocked
request transitions to unlocked
because the lock was either released or invalidated by a concurrent request which took precedenceunlocked
request transitions to locked
because the requesting member reacquired a lockNo vulnerabilities found.
No security vulnerabilities found.