diff --git a/README.md b/README.md index ae15d17c..2e418ea6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Ably Collaborative Spaces SDK +# Ably Spaces SDK

@@ -12,53 +12,57 @@

-The [Ably](https://ably.com) Collaborative Spaces SDK enables you to implement realtime collaborative features in your applications. +_[Ably](https://ably.com) 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._ -![Example collaboration GIF](/docs/images/collab.gif) +--- -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. Try out a [live demo](https://space.ably.dev) of a slideshow application for an example of realtime collaboration in action. +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](https://space.ably.dev) of a slideshow application for an example of realtime collaboration powered by the Spaces SDK. -Realtime collaboration enables users to have contextual awareness of other users within an application. This means knowing: +![Example collaboration GIF](/docs/images/collab.gif) -**Who is in the application?** -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. +## Realtime collaboration -**Where is each user within the application?** +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: -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 slideshow 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. +### Who is in the application? + +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. -**What is everyone doing in the application?** +### Where is each user within the application? -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. Live updates help accomplish this in a collaborative space. +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". -## Status +### What is everyone doing in the application? -The Collaborative Spaces SDK is currently under development. If you are interested in being an early adopter and providing feedback then you can [sign up](https://go.ably.com/spaces-early-access) for early access and are welcome to [provide us with feedback](https://go.ably.com/spaces-feedback). +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. -## Quickstart +## SDK Development Status -Get started quickly using this section, or take a look at: +The Spaces SDK is currently under development. If you are interested in being an early adopter and providing feedback then you can [sign up](https://go.ably.com/spaces-early-access) for early access and are welcome to [provide us with feedback](https://go.ably.com/spaces-feedback). -* more detailed [usage instructions](/docs/usage.md) -* [class definitions](/docs/class-definitions.md) -* how the Spaces SDK uses [Ably internally](/docs/channel-behaviors.md) +The next section gives you an overview of how to use the SDK. Alternatively, you can jump to: +* [Class definitions](/docs/class-definitions.md) +* [Usage instructions](/docs/usage.md) +* [Channel behaviors](/docs/channel-behaviors.md) -### Prerequisites +## Prerequisites -To begin, you will need the following: +To start using this SDK, you will need the following: * An Ably account - * You can [sign up](https://ably.com/signup) for free + * You can [sign up](https://ably.com/signup) to the generous free tier. * An Ably API key - * Create API keys in an app within your [Ably account](https://ably.com/dashboard) - * The API key needs the following [capabilities](https://ably.com/docs/auth/capabilities): `publish`, `subscribe`, `presence` and `history`. + * Use the default or create a new API key in an app within your [Ably account dashboard](https://ably.com/dashboard). + * Make sure your API key has the following [capabilities](https://ably.com/docs/auth/capabilities): `publish`, `subscribe`, `presence` and `history`. -You can use [basic authentication](https://ably.com/docs/auth/basic) for testing purposes, however it is strongly recommended that you use [token authentication](https://ably.com/docs/auth/token) in production environments. -### Authenticate and instantiate -Install the Collaborative Spaces SDK and the Ably JavaScript SDK: +## Installation and authentication + +#### Option 1: Using NPM + +Install the Ably JavaScript SDK and the Spaces SDK: ```sh npm install ably @ably-labs/spaces @@ -73,10 +77,11 @@ import { Realtime } from 'ably'; const client = new Realtime.Promise({key: "", clientId: ""}); const spaces = new Spaces(client); ``` +You can use [basic authentication](https://ably.com/docs/auth/basic) i.e. the API Key directly for testing purposes, however it is strongly recommended that you use [token authentication](https://ably.com/docs/auth/token) in production environments. -You can create an Ably client with just an API key, however to use Spaces you must also set a [`clientId`](https://ably.com/docs/auth/identified-clients) so that clients are identifiable. If you are prototyping, you can use a package like [nanoid](https://www.npmjs.com/package/nanoid) to generate an ID. +To use Spaces you must also set a [`clientId`](https://ably.com/docs/auth/identified-clients) so that clients are identifiable. If you are prototyping, you can use a package like [nanoid](https://www.npmjs.com/package/nanoid) to generate an ID. -#### CDN +#### Option 2: Using a CDN You can also use Spaces with a CDN, such as [unpkg](https://www.unpkg.com/): @@ -84,10 +89,13 @@ You can also use Spaces with a CDN, such as [unpkg](https://www.unpkg.com/): ``` +After this, instantiate the SDK in the same way as in the NPM option above. -### Space +## Creating a new Space -A space is the virtual, collaborative area of an application you want to monitor. A space can be anything from a web page, a sheet within a spreadsheet, an individual slide in a slideshow, or the slideshow itself. Create a space and listen for events to see when clients enter and leave. +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. ```ts // Create a new space @@ -105,7 +113,7 @@ space.enter({ }); ``` -The following is an example `update` event received by listeners when a user enters a space: +The following is an example event payload received by subscribers when a user enters a space: ```json [ @@ -126,33 +134,38 @@ The following is an example `update` event received by listeners when a user ent ] ``` -### Members +## Space members -Members is used to build avatar stacks and find out which members are online within a space. +The `members` namespace within a Space is a client-side filtered listener optimized for building avatar stacks. Subscribe to members entering, leaving, being removed from the Space (after a timeout) or updating their profile information. ```ts -// Subscribe to all member events in a space by passing the 'update' event -space.members.subscribe('update', (memberUpdate) => { +// Subscribe to all member events in a space +space.members.subscribe((memberUpdate) => { console.log(memberUpdate); }); -// Subscribe to when a member enters the space +// Subscribe to member enter events only space.members.subscribe('enter', (memberJoined) => { console.log(memberJoined); }); -// Subscribe to when a member leaves the space +// Subscribe to member leave events only space.members.subscribe('leave', (memberLeft) => { console.log(memberLeft); }); -// Subscribe to when a member is removed from the space +// Subscribe to member remove events only space.members.subscribe('remove', (memberRemoved) => { console.log(memberRemoved); }); + +// Subscribe to member profile update events only +space.members.subscribe('update', (memberProfileUpdated) => { + console.log(memberProfileUpdated); +}); ``` -The following is an example `memberUpdate` event received by listeners: +The following is an example event payload received by subscribers when member updates occur in a space: ```json { @@ -171,7 +184,9 @@ The following is an example `memberUpdate` event received by listeners: } ``` -Members has methods to get the current snapshot of member state: +### Getting a snapshot of space members + +Space members has methods to get the current snapshot of member state: ```ts // Get all members in a space @@ -184,27 +199,28 @@ const myMemberInfo = await space.members.getSelf(); const othersMemberInfo = await space.members.getOthers(); ``` -### Location +## Member locations -Member locations enable 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. +The `locations` namespace within a Space is a client-side filtered listener optimized for building member locations which enable 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. ```ts -// Register a listener to subscribe to events of when users change location -space.locations.subscribe('update', (locationUpdate) => { - console.log(locationUpdate); -}); - -// You need to enter a space before setting your location +// You need to enter a space before publishing your location space.enter({ username: 'Claire Lemons', avatar: 'https://slides-internal.com/users/clemons.png', }); -// Publish locationUpdate event with a client's location when they select a UI element +// Publish your location based on the UI element selected space.locations.set({ slide: '3', component: 'slide-title' }); + +// Subscribe to location events from all members in a space +space.locations.subscribe('update', (locationUpdate) => { + console.log(locationUpdate); +}); + ``` -The following is an example `locationUpdate` event received by subscribers when a user changes location: +The following is an example event payload received by subscribers when a member changes location: ```json { @@ -236,29 +252,44 @@ The following is an example `locationUpdate` event received by subscribers when } ``` -### Cursors +### Getting a snapshot of member locations -Use the Cursors API to track client pointer events across an application. Events can also include associated data, such as pointer attributes and the IDs of associated UI elements: +Member locations has methods to get the current snapshot of member state: ```ts -// You need to enter a space before setting your cursor updates +// Get a snapshot of all the member locations +const allLocations = space.locations.getAll(); + +// Get a snapshot of my location +const myLocation = space.locations.getSelf(); + +// Get a snapshot of everyone else's locations +const othersLocations = space.locations.getOthers(); +``` + +## Live cursors + +The `cursors` namespace within a Space is a client-side filtered listener optimized for building live cursors which allows 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: + +```ts +// You need to enter a space before publishing your cursor updates space.enter({ username: 'Claire Lemons', avatar: 'https://slides-internal.com/users/clemons.png', }); -// Listen to events published on "mousemove" by all members +// Subscribe to events published on "mousemove" by all members space.cursors.subscribe('update', (cursorUpdate) => { console.log(cursorUpdate); }); -// Publish a CursorUpdate with the location of a mouse, including optional data for the current member +// Publish a your cursor position on "mousemove" including optional data window.addEventListener('mousemove', ({ clientX, clientY }) => { space.cursors.set({ position: { x: clientX, y: clientY }, data: { color: 'red' } }); }); ``` -The above listener will receive a `CursorUpdate` event: +The following is an example event payload received by subscribers when a member moves their cursor: ```js { @@ -268,3 +299,18 @@ The above listener will receive a `CursorUpdate` event: "data": { "color": "red" } } ``` + +### Getting a snapshot of member cursors + +Member cursors has methods to get the current snapshot of member state: + +```ts +// Get a snapshot of all the cursors +const allCursors = space.cursors.getAll(); + +// Get a snapshot of my cursor +const myCursor = space.cursors.getSelf(); + +// Get a snapshot of everyone else's cursors +const othersCursors = space.cursors.getOthers(); +``` diff --git a/docs/class-definitions.md b/docs/class-definitions.md index 0ef33e7f..fdc4fce7 100644 --- a/docs/class-definitions.md +++ b/docs/class-definitions.md @@ -2,19 +2,7 @@ ## Constructor -Create a new instance of the Spaces library. - -The Spaces library constructor is overloaded allowing it to be instantiated using a [ClientOptions](https://ably.com/docs/api/realtime-sdk?lang=javascript#client-options) object: - -_**Deprecated: the ClientOptions option will be removed in the next release. Use the Ably client instance method described underneath.**_ - -```ts -import Spaces from '@ably-labs/spaces'; - -const spaces = new Spaces({ key: "", clientId: "" }); -``` - -Or an instance of the realtime, promise-based [Ably client](https://github.com/ably/ably-js): +Create a new instance of the Space SDK by passing an instance of the realtime, promise-based [Ably client](https://github.com/ably/ably-js): ```ts import { Realtime } from 'ably/promise'; @@ -24,15 +12,15 @@ const client = new Realtime.Promise({ key: "", clientId: "" const spaces = new Spaces(client); ``` -In both cases, a [clientId](https://ably.com/docs/auth/identified-clients?lang=javascript) is required. +Please note that a [clientId](https://ably.com/docs/auth/identified-clients?lang=javascript) is required. An API key will required for [basic authentication](https://ably.com/docs/auth/basic?lang=javascript). We strongly recommended that you use [token authentication](https://ably.com/docs/realtime/authentication#token-authentication) in any production environments. Refer to the [Ably docs for the JS SDK](https://ably.com/docs/getting-started/setup?lang=javascript) for information on setting up a realtime promise client. -## Properties +### Properties -### ably +#### ably Instance of the [Ably-JS](https://github.com/ably/ably-js#introduction) client that was passed to the [constructor](#constructor). @@ -40,7 +28,7 @@ Instance of the [Ably-JS](https://github.com/ably/ably-js#introduction) client t type ably = Ably.RealtimePromise; ``` -### version +#### version Version of the Spaces library. @@ -48,9 +36,9 @@ Version of the Spaces library. type version = string; ``` -## Methods +### Methods -### get +#### get() Get or create a Space instance. Returns a [Space](#space) instance. Configure the space by passing [SpaceOptions](#spaceoptions) as the second argument. @@ -58,9 +46,9 @@ Get or create a Space instance. Returns a [Space](#space) instance. Configure th type get = (name: string, options?: SpaceOptions) => Promise; ``` -## Related Types +### Related Types -### SpaceOptions +#### SpaceOptions Used to configure a Space instance on creation. @@ -71,15 +59,15 @@ type SpaceOptions = { }; ``` -#### offlineTimeout +##### offlineTimeout -Number of milliseconds after a user loses connection or closes their browser window to wait before their [SpaceMember](#spacemember) object is removed from the members list. The default is 120000ms (2 minutes). +Number of milliseconds after a user loses connection or closes their browser window to wait before their [SpaceMember](#spacemember) object is removed from the members list. The default is 120000ms (2 minutes). -#### cursors +##### cursors Options relating to configuring the cursors API (see below). -#### CursorsOptions +##### CursorsOptions ```ts type CursorsOptions = { @@ -96,13 +84,13 @@ The interval in milliseconds at which a batch of cursor positions are published. The number of pages searched from [history](https://ably.com/docs/storage-history/history) for the last published cursor position. The default is 5. -# Space +## Space An instance of a Space created using [spaces.get](#get). Inherits from [EventEmitter](/docs/usage.md#event-emitters). -## Properties +### Properties -### members +#### members An instance of [Members](#members). @@ -110,7 +98,7 @@ An instance of [Members](#members). type members = instanceof Members; ``` -### cursors +#### cursors An instance of [Cursors](#cursors). @@ -118,7 +106,7 @@ An instance of [Cursors](#cursors). type cursors = instanceof Cursors; ``` -### locations +#### locations An instance of [Locations](#locations). @@ -126,9 +114,9 @@ An instance of [Locations](#locations). type locations = instanceof Locations; ``` -## Methods +### Methods -### enter +#### enter() Enter the space. Can optionally take `profileData`. This data can be an arbitrary JSON-serializable object which will be attached to the [member object](#spacemember). Returns all current space members. @@ -136,7 +124,7 @@ Enter the space. Can optionally take `profileData`. This data can be an arbitrar type enter = (profileData?: Record) => Promise; ``` -### leave +#### leave() Leave the space. Can optionally take `profileData`. This triggers the `leave` event, but does not immediately remove the member from the space. See [offlineTimeout](#spaceoptions). @@ -144,7 +132,7 @@ Leave the space. Can optionally take `profileData`. This triggers the `leave` ev type leave = (profileData?: Record) => Promise; ``` -### updateProfileData +#### updateProfileData() Update `profileData`. This data can be an arbitrary JSON-serializable object which is attached to the [member object](#spacemember). If the connection has not entered the space, calling `updateProfileData` will call `enter` instead. @@ -162,29 +150,25 @@ await space.updateProfileData((oldProfileData) => { }) ``` -# Members +## Space Members Handles members within a space. -## Methods - -### subscribe +### Methods -Listen to member events for the space. See [EventEmitter](/docs/usage.md#event-emitters) for overloading usage. +#### subscribe() -Available events: - -- #### **update** - - Listen to profile data updates of members. +Listen to member events for the space. See [EventEmitter](/docs/usage.md#event-emitters) for overloaded usage. ```ts - space.members.subscribe('update', (member: SpaceMember) => {}); + space.members.subscribe((member: SpaceMember) => {}); ``` The argument supplied to the callback is the [SpaceMember](#spacemember) object representing the member that triggered the event. -- #### **enter** + Available events: + +- ##### **enter** Listen to enter events of members. @@ -193,7 +177,7 @@ Available events: ``` The argument supplied to the callback is a [SpaceMember](#spacemember) object representing the member entering the space. -- #### **leave** +- ##### **leave** Listen to leave events of members. The leave event will be issued when a member calls `space.leave()` or is disconnected. @@ -203,9 +187,9 @@ Available events: The argument supplied to the callback is a [SpaceMember](#spacemember) object representing the member leaving the space. -- #### **remove** +- ##### **remove** - Listen to remove events of members. The remove event will be issued when the [offlineTimeout](#spaceoptions) has passed. + Listen to remove events of members. The remove event will be triggered when the [offlineTimeout](#spaceoptions) has passed. ```ts space.members.subscribe('remove', (member: SpaceMember) => {}) @@ -213,9 +197,19 @@ Available events: The argument supplied to the callback is a [SpaceMember](#spacemember) object representing the member removed from the space. -### unsubscribe +- ##### **update** -Remove all event listeners, all event listeners for an event, or specific listeners. See [EventEmitter](/docs/usage.md#event-emitters) for detailed usage. + Listen to profile update events of members. + + ```ts + space.members.subscribe('update', (member: SpaceMember) => {}) + ``` + The argument supplied to the callback is a [SpaceMember](#spacemember) object representing the member entering the space. + + +#### unsubscribe() + +Remove all the event listeners or specific listeners. See [EventEmitter](/docs/usage.md#event-emitters) for detailed usage. ```ts // Unsubscribe from all events @@ -228,7 +222,7 @@ space.members.unsubscribe('enter'); space.members.unsubscribe('leave'); ``` -### getSelf +#### getSelf() Returns a Promise which resolves to the [SpaceMember](#spacemember) object relating to the local connection. Will resolve to `undefined` if the client hasn't entered the space yet. @@ -242,7 +236,7 @@ Example: const myMember = await space.members.getSelf(); ``` -### getAll +#### getAll() Returns a Promise which resolves to an array of all [SpaceMember](#spacemember) objects (members) currently in the space, including any who have left and not yet timed out. (_see: [offlineTimeout](#spaceoptions)_) @@ -256,7 +250,7 @@ Example: const allMembers = await space.members.getAll(); ``` -### getOthers +#### getOthers() Returns a Promise which resolves to an array of all [SpaceMember](#spacemember) objects (members) currently in the space, excluding your own member object. @@ -270,9 +264,9 @@ Example: const otherMembers = await space.members.getOthers(); ``` -## Related Types +### Related Types -### SpaceMember +#### SpaceMember A SpaceMember represents a member within a Space instance. Each new connection that enters will create a new member, even if they have the same [`clientId`](https://ably.com/docs/auth/identified-clients?lang=javascript). @@ -287,31 +281,31 @@ type SpaceMember = { }; ``` -#### clientId +##### clientId The client identifier for the user, provided to the ably client instance. -#### connectionId +##### connectionId Identifier for the connection used by the user. This is a unique identifier. -#### isConnected +##### isConnected Whether the user is connected to Ably. -#### profileData +##### profileData Optional user data that can be attached to a user, such as a username or image to display in an avatar stack. -#### location +##### location The current location of the user within the space. -#### lastEvent +##### lastEvent The most recent event emitted by [presence](https://ably.com/docs/presence-occupancy/presence?lang=javascript) and its timestamp. Events will be either `enter`, `leave`, `update` or `present`. -### PresenceEvent +#### PresenceEvent ```ts type PresenceEvent = { @@ -320,19 +314,19 @@ type PresenceEvent = { }; ``` -# Locations +## Member Locations Handles the tracking of member locations within a space. Inherits from [EventEmitter](/docs/usage.md#event-emitters). -## Methods +### Methods -### subscribe +#### subscribe() -Listen to events for locations. See [EventEmitter](/docs/usage.md#event-emitters) for overloading usage. +Listen to events for locations. See [EventEmitter](/docs/usage.md#event-emitters) for overloaded usage. Available events: -- #### **update** +- ##### **update** Fires when a member updates their location. The argument supplied to the event listener is a [LocationUpdate](#locationupdate-1). @@ -340,7 +334,7 @@ Available events: space.locations.subscribe('update', (locationUpdate: LocationUpdate) => {}); ``` -### set +#### set() Set your current location. [Location](#location-1) can be any JSON-serializable object. Emits a [locationUpdate](#locationupdate) event to all connected clients in this space. @@ -348,7 +342,7 @@ Set your current location. [Location](#location-1) can be any JSON-serializable type set = (update: Location) => Promise; ``` -### unsubscribe +#### unsubscribe() Remove all event listeners, all event listeners for an event, or specific listeners. See [EventEmitter](/docs/usage.md#event-emitters) for detailed usage. @@ -356,7 +350,7 @@ Remove all event listeners, all event listeners for an event, or specific listen space.locations.unsubscribe('update'); ``` -### getSelf +#### getSelf() Get location for self. @@ -370,7 +364,7 @@ Example: const myLocation = await space.locations.getSelf(); ``` -### getAll +#### getAll() Get location for all members. @@ -384,7 +378,7 @@ Example: const allLocations = await space.locations.getAll(); ``` -### getOthers +#### getOthers() Get location for other members @@ -398,9 +392,9 @@ Example: const otherLocations = await space.locations.getOthers() ``` -## Related types +### Related types -### Location +#### Location Represents a location in an application. @@ -408,7 +402,7 @@ Represents a location in an application. type Location = string | Record | null; ``` -### LocationUpdate +#### LocationUpdate Represents a change between locations for a given [`SpaceMember`](#spacemember). @@ -420,19 +414,19 @@ type LocationUpdate = { }; ``` -# Cursors +## Live Cursors Handles tracking of member cursors within a space. Inherits from [EventEmitter](/docs/usage.md#event-emitters). -## Methods +### Methods -### subscribe +#### subscribe() Listen to `CursorUpdate` events. See [EventEmitter](/docs/usage.md#event-emitters) for overloaded usage. Available events: -- #### **update** +- ##### **update** Emits an event when a new cursor position is set. The argument supplied to the event listener is a [CursorUpdate](#cursorupdate). @@ -440,11 +434,11 @@ Available events: space.cursors.subscribe('update', (cursorUpdate: CursorUpdate) => {}); ``` -### set +#### set() -Set the position of a cursor. This will emit a `CursorUpdate` event. If a member has not yet entered the space, this method will error. +Set the position of a cursor. If a member has not yet entered the space, this method will error. -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): +A event payload returned contains 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): ```ts type set = (update: { position: CursorPosition, data?: CursorData }) @@ -458,7 +452,7 @@ window.addEventListener('mousemove', ({ clientX, clientY }) => { }); ``` -### unsubscribe +#### unsubscribe() Remove all event listeners, all event listeners for an event, or specific listeners. See [EventEmitter](/docs/usage.md#event-emitters) for detailed usage. @@ -466,9 +460,9 @@ Remove all event listeners, all event listeners for an event, or specific listen space.cursors.unsubscribe('update'); ``` -### getSelf +#### getSelf() -Get the last CursorUpdate for self. +Get the last `CursorUpdate` object for self. ```ts type getSelf = () => ; @@ -480,9 +474,9 @@ Example: const selfPosition = space.cursors.getSelf(); ``` -### getAll +#### getAll() -Get the last CursorUpdate for each connection. +Get the last `CursorUpdate` object for all the members. ```ts type getAll = () => Record; @@ -494,9 +488,9 @@ Example: const allLatestPositions = space.cursors.getAll(); ``` -### getOthers +#### getOthers() -Get the last CursorUpdate for each connection. +Get the last `CursorUpdate` object for everyone else but yourself. ```ts type getOthers = () => Record; @@ -508,9 +502,9 @@ Example: const otherPositions = space.cursors.getOthers(); ``` -## Related types +### Related types -### CursorUpdate +#### CursorUpdate Represents an update to a cursor. @@ -524,7 +518,7 @@ type CursorUpdate = { }; ``` -### CursorPosition +#### CursorPosition Represents a cursors position. @@ -535,7 +529,7 @@ type CursorPosition = { }; ``` -### CursorData +#### CursorData Represent data that can be associated with a cursor update. diff --git a/docs/usage.md b/docs/usage.md index ae199061..62d22c35 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,16 +1,18 @@ -# Usage +# Usage Instructions + +This page contains detailed documentation to help you use the Spaces SDK. ## Prerequisites ### Ably API key -To use Spaces, you will need the following: +To start using this SDK, you will need the following: * An Ably account - * You can [sign up](https://ably.com/signup) for free + * You can [sign up](https://ably.com/signup) to the generous free tier. * An Ably API key - * Create API keys in an app within your [Ably account](https://ably.com/dashboard) - * The API key needs the following [capabilities](https://ably.com/docs/realtime/authentication#capabilities-explained): `publish`, `subscribe`, `presence` and `history` + * Use the default or create a new API key in an app within your [Ably account dashboard](https://ably.com/dashboard). + * Make sure your API key has the following [capabilities](https://ably.com/docs/auth/capabilities): `publish`, `subscribe`, `presence` and `history`. ### Environment @@ -20,14 +22,10 @@ Spaces is built on top of the [Ably JavaScript SDK](https://github.com/ably/ably ### NPM -```sh -npm install @ably-labs/spaces -``` - -If you need the Ably client (see [Authentication & instantiation](#authentication-and-instantiation)) +You'll need to install both the ably client and the spaces client: ```sh -npm install ably +npm install ably @ably-labs/spaces ``` ### CDN @@ -39,23 +37,11 @@ You can also use Spaces with a CDN like [unpkg](https://www.unpkg.com/): ``` -Note that when you use a CDN, you need to include Ably Client as well, the Spaces bundle does not include it. - ## Authentication and instantiation -Spaces use an [Ably promise-based realtime client](https://github.com/ably/ably-js#using-the-async-api-style). You can either pass an existing client to Spaces or pass the [client options](https://ably.com/docs/api/realtime-sdk?lang=javascript#client-options) directly to the spaces constructor. - -To instantiate with options, you will need at minimum an [Ably API key](#ably-api-key) and a [clientId](https://ably.com/docs/auth/identified-clients?lang=javascripts). A clientId represents an identity of an connection. In most cases this will something like the id of a user: - -_**Deprecated: the ClientOptions option will be removed in the next release. Use the Ably client instance method described underneath.**_ - -```ts -import Spaces from '@ably-labs/spaces'; - -const spaces = new Spaces({ key: "", clientId: "" }); -``` +Spaces use an [Ably promise-based realtime client](https://github.com/ably/ably-js#using-the-async-api-style). You can pass an Ably client directly to the spaces constructor. -If you already have an ably client in your application, you can just pass it directly to Spaces (the client will still need to instantiated with a clientId): +The Ably client instantiation accepts client options. You will need at minimum an [Ably API key](#ably-api-key) and a [clientId](https://ably.com/docs/auth/identified-clients?lang=javascripts). A clientId represents an identity of an connection. In most cases this will something like the id of a user: ```ts import { Realtime } from 'ably/promise'; @@ -65,13 +51,13 @@ const client = new Realtime.Promise({ key: "", clientId: "" const spaces = new Spaces(client); ``` -In both scenarios, you can access the client via `spaces.ably`. +You can access the Ably client via `spaces.ably`. To learn more about authenticating with ably, see our [authentication documentation](https://ably.com/docs/auth). ## Create a space -A space is the virtual area of an application you want 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": +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": ```ts const space = await spaces.get('demoSlideshow'); @@ -130,7 +116,7 @@ A leave event is sent when a user leaves a space. This can occur for one of the - 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 members. Instead, they are removed after a timeout which is configurable by the [`offlineTimeout` option](#options). This allows the UI to display an intermediate state before disconnection/reconnection. +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](#options). This allows the UI to display an intermediate state before disconnection/reconnection. As with `enter`, you can update the `profileData` on leave: @@ -143,7 +129,7 @@ space.leave({ ### Update profileData -To update `profileData` provided when entering the space, use the `updateProfileData` method. Pass new `profileData` or a function to base the new `profileData` of the existing value: +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: ```ts await space.updateProfileData((oldProfileData) => { @@ -179,15 +165,21 @@ See [SpaceMember](/docs/class-definitions.md#spacemember) for details on propert ### Member events -Subscribe to either `enter`, `leave`, `remove` or `update` events related to members in a space. +Subscribe to either all the member events or specifically to `enter`, `leave`, `remove` or `update` events related to members in a space. + +```ts +space.members.subscribe((memberUpdate) => { + console.log(memberUpdate); +}); +``` #### enter Emitted when a member enters a space. Called with the member entering the space. ```ts -space.members.subscribe('enter', (memberJoins) => { - console.log(memberJoins); +space.members.subscribe('enter', (memberJoined) => { + console.log(memberJoined); }); ``` @@ -213,20 +205,11 @@ space.members.subscribe('remove', (memberRemoved) => { #### update -Emitted when for `enter`, `leave` and `remove` events in a space: - +Emitted when a member updates their `profileData` via `space.updateProfileData()`: ```ts -space.members.subscribe('update', (memberUpdate) => { - console.log(memberUpdate); -}); -``` - -This is the same as not specifying the event: - -```ts -space.members.subscribe((memberUpdate) => { - console.log(memberUpdate); +space.members.subscribe('update', (memberProfileUpdated) => { + console.log(memberProfileUpdated); }); ``` @@ -247,21 +230,14 @@ The location property will be set on the [member](#members). A location event will be emitted when a member updates their location: -```ts -space.subscribe('update', (member) => { - console.log(member.location); -}); -``` -When a member leaves, their location is set to `null`. - -However, it's possible to listen to just location updates. `locations` is an [event emitter](#event-emitters) and will emit the `locationUpdate` event: - ```ts 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: ```json @@ -300,7 +276,7 @@ A common feature of collaborative apps is to show where a users cursors is posit The most common use case is to show the current mouse pointer position. -To start listing to cursor events, use the `.subscribe` method: +To start listening to cursor events, use the `subscribe()` method: ```ts space.cursors.subscribe('update', (cursorUpdate) => { @@ -319,7 +295,7 @@ The listener will be called with a `CursorUpdate`: } ``` -To set the position of a cursor and emit a `CursorUpdate`, first enter the space: +To set the position of a cursor and emit a `CursorUpdate`, first enter the space if you haven't already: ```ts space.enter(); @@ -369,51 +345,51 @@ const lastPositions = await space.cursors.getAll(); ## Event Emitters -`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. +`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. +Calling `subscribe()` with a single function argument will subscribe to all events on that emitter. ```ts space.members.subscribe(); ``` -Calling `subscribe` with a named event and a function argument will subscribe to that event only. +Calling `subscribe()` with a named event and a function argument will subscribe to that event only. ```ts -space.members.subscribe(`enter`, () => {}); +space.members.subscribe('enter', () => {}); ``` -Calling `subscribe` with an array of named events and a function argument will subscribe to those events. +Calling `subscribe()` with an array of named events and a function argument will subscribe to those events. ```ts -space.members.subscribe([`enter`, `leave`], () => {}); +space.members.subscribe(['enter', 'leave'], () => {}); ``` -Calling `unsubscribe` with no arguments will remove all registered listeners. +Calling `unsubscribe()` with no arguments will remove all registered listeners. ```ts space.members.unsubscribe(); ``` -Calling `unsubscribe` with a single named event will remove all listeners registered for that event. +Calling `unsubscribe()` with a single named event will remove all listeners registered for that event. ```ts -space.members.unsubscribe(`enter`); +space.members.unsubscribe('enter'); ``` -Calling `unsubscribe` with an array of named events will remove all listeners registered for those events. +Calling `unsubscribe()` with an array of named events will remove all listeners registered for those events. ```ts -space.members.unsubscribe([`enter`, `leave`]); +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. +Calling `unsubscribe()` and adding a listener function as the second argument to both of the above will remove only that listener. ```ts const listener = () => {}; -space.members.unsubscribe(`update`, listener); -space.members.unsubscribe([`update`], 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`. +As with the native DOM API, this only works if the listener is the same reference as the one passed to `subscribe()`.