Here’s how to migrate from ably-js v1 to v2:
- Stop using functionality that v1 deprecated and which v2 removes.
- Stop using other functionality that v2 removes.
- Update to v2 and handle its breaking changes.
- (Optional) Stop using functionality that v2 deprecates.
- (Optional) Take advantage of new features that v2 introduces.
Begin by updating to ably-js 1.2.50 or later, to make sure you see the deprecation log messages described below.
Now, you need to stop using the functionality that is deprecated by v1 and which is removed in v2. Here we explain how.
The changes below are split into:
- general changes
- changes that only affect TypeScript users
If you’re not using any of the deprecated functionality described below, then this section does not affect you.
In ably-js 1.2.50 or later, use of the following APIs will trigger a deprecation warning at runtime.
Note about deprecation warnings: These deprecation warnings take the form of error-level log messages emitted through the library’s logging mechanism (i.e. ClientOptions.log.handler
or ClientOptions.logHandler
if you’ve set these properties, or else console.log()
). To find them in your logs, search for Ably: Deprecation warning
.
- The
log
client option has been removed in v2. Equivalent functionality is provided by thelogLevel
andlogHandler
client options. Update your client options code of the form{ log: { level: logLevel, handler: logHandler } }
to instead be{ logLevel, logHandler }
. - Changes to
Crypto.getDefaultParams()
:- The ability to pass a callback to this method has been removed in v2. This method now directly returns its result, instead of returning it asynchronously. Update your code so that it uses the return value of this method instead of passing a callback.
- The ability to call this method without specifying an encryption key has been removed in v2. Update your code so that it instead passes an object whose
key
property contains an encryption key. That is, replaceCrypto.getDefaultParams()
withCrypto.getDefaultParams({ key })
, wherekey
is an encryption key that you have generated (for example from theCrypto.generateRandomKey()
method). - The ability to pass the encryption key as the first argument of this method has been removed in v2. Update your code so that it instead passes an object whose
key
property contains the key. That is, replaceCrypto.getDefaultParams(key)
withCrypto.getDefaultParams({ key })
.
- The
fallbackHostsUseDefault
client option has been removed in v2.- If you’re using this client option to force the library to make use of fallback hosts even though you’ve set the
environment
client option, then this is no longer necessary: remove your usage of thefallbackHostsUseDefault
client option and the library will then automatically choose the correct fallback hosts to use for the specified environment. - If you’re using this client option to force the library to make use of fallback hosts even though you’re not using the primary Ably environment, then stop using
fallbackHostsUseDefault
, and update your code to either pass theenvironment
client option (in which case the library will automatically choose the correct fallback hosts to use for the specified environment), or to pass thefallbackHosts
client option to specify a custom list of fallback hosts to use (for example, if you’re using a custom CNAME, in which case Ably will have provided you with an explicit list of fallback hosts).
- If you’re using this client option to force the library to make use of fallback hosts even though you’ve set the
- The ability to use a boolean value for the
recover
client option has been removed in v2. If you wish for the connection to always be recovered, replace{ recover: true }
with a function that always passestrue
to its callback:{ recover: function(lastConnectionDetails, cb) { cb(true); } }
. - The ability to pass an array of channel mode flags as the first argument of
RealtimeChannel.attach()
has been removed in v2. To set channel mode flags, populate themodes
property of the channel options object that you pass toChannels.get()
orRealtimeChannel.setOptions()
. - The
force
auth option has been removed in v2. If you’re using this option to forceauthorize()
to fetch a new token even if the current token has not expired, this is no longer necessary, asauthorize()
now always fetches a new token. Update your code to no longer pass theforce
auth option. Note that, in general, passing an auth options argument toauthorize()
will overwrite the library’s stored auth options, which may not be what you want. In v1, the library contains a special case behavior where passing an auth options object which only contains{ force: true }
will not overwrite the stored options. This special case behavior has been removed in v2, so if you’re currently passingauthorize()
an auth options object which only contains{ force: true }
, you should stop passing it an auth options object entirely. - The
host
client option has been renamed torestHost
. Update your code to userestHost
. - The
wsHost
client option has been renamed torealtimeHost
. Update your code to userealtimeHost
. - The
queueEvents
client option has been renamed toqueueMessages
. Update your code to usequeueMessages
. RealtimePresence
’son
method has been renamed tosubscribe
. Update your code to usesubscribe
.RealtimePresence
’soff
method has been renamed tounsubscribe
. Update your code to useunsubscribe
.Auth
’sauthorise
method has been renamed toauthorize
. Update your code to useauthorize
.- The
headers
client option has been removed in v2. Remove your use of this client option.
- In v2, the
stats()
method onRest
andRealtime
no longer accepts an argument of typeany
. Make sure that any argument you pass to this method implements theStatsParams
interface. - In v2,
fromEncoded<T>
andfromEncodedArray<T>
types, which were already not being used by the library, are no longer exported by the library. Remove your references to these types.
Note: This section is only relevant if you’re not already using v1’s promise-based API.
ably-js v1 offered a choice between styles of asynchronous programming. There was a variant of the library that implemented asynchronous function calls via callbacks, and another that implemented them via promises. Now that promises are widely supported across modern JavaScript engines, ably-js v2 removes the callbacks variant of the library, and only offers promise-based asynchronicity.
So, if you’re currently using the callbacks variant of the library, then before upgrading to v2 you should switch to using the promises variant. Here, we explain how to do this.
First, you should stop choosing the callbacks variant of the library, and instead choose the promises variant. How exactly you should change your code to achieve this depends on how you’re currently choosing the callbacks variant:
- If you’re implicitly choosing the callbacks variant of the library by writing
require('ably')
, then update your code torequire('ably/promises')
. - If you’re explicitly choosing the callbacks variant through the subpath of the imported module — that is, if you’re writing
require('ably/callbacks')
— then update your code torequire('ably/promises')
. - If you’re explicitly choosing the callbacks variant at the moment of instantiating the client — that is, if you’re writing
new Ably.Rest.Callbacks(…)
ornew Ably.Realtime.Callbacks(…)
— then update your code to use thePromise
property instead, i.e. writenew Ably.Realtime.Promise(…)
ornew Ably.Rest.Promise(…)
.
Now, update your code to use ably-js’s promise-based API instead of the callback-based API. The best way to do this is to consult the documentation for v1’s promise-based API, to find the documentation for the promise-based version of each method that you’re using in your code. If you don’t want to do this, it’s generally sufficient to understand that the promise-based API differs from the callback-based API in a way that’s consistent across all methods. What follows is a description of this difference.
Given a method which, in the callback-based API, takes a callback of the form (err, result)
as its final argument, its equivalent in the promise-based API does not take this final argument. Instead, it returns a promise. If the operation succeeds then this promise will be resolved with a value equivalent to the callback’s result
argument, and if it fails then this promise will be rejected with a value equivalent to the callback’s err
argument.
So, you need to update your code to stop passing this callback, and then make use of the returned promise. In general, JavaScript offers a couple of approaches for interacting with promises, and so now we’ll demonstrate how these apply to an example ably-js method call.
For example, given the following call to the callbacks-based version of RestChannel.history()
:
channel.history({ direction: 'forwards' }, (err, paginatedResult) => {
if (err) {
// Perform some sort of error handling
return;
}
// Make use of paginatedResult
});
We could do one of the following:
-
Use JavaScript’s
await
keyword to retrieve the result of the operation, combined with thecatch
keyword for error handling:try { const paginatedResult = await channel.history({ direction: 'forwards' }); // Make use of paginatedResult } catch (err) { // Perform some sort of error handling }
-
Use the promise’s
then
method to retrieve the result of the operation, combined with itscatch
method for error handling:channel .history({ direction: 'forwards' }) .then((paginatedResult) => { // Make use of paginatedResult }) .catch((err) => { // Perform some sort of error handling });
Important: For historical reasons, the Crypto.generateRandomKey()
method does not have a promise-based version in v1. That is, even in the promise-based variant of the SDK, it implements asynchronicity via callbacks. So, if you’re using this method, then you’ll need to keep using the callback-based version of this method until upgrading to v2, which replaces the callback-based variant of this method with a promise-based one. For more information see here.
Next, update to ably-js version 2.0.0 or later.
Now, you need to address the other breaking changes introduced by v2. Here we explain how.
The changes below are split into:
- general changes
- changes that only affect TypeScript users
Some of these changes are only relevant if you’re using specific features of the library. The guidance below makes it clear when this is the case.
As mentioned above, v2 of the library no longer offers a choice between a callbacks-based API and a promises-based API. This means that v1’s mechanism for choosing which variant to use has been removed, so you should stop using this mechanism.
- If you’re explicitly choosing the promises variant of the library through the subpath of the imported module — that is, if you’re writing
require('ably/promises')
— then update your code torequire('ably')
. - If you’re explicitly choosing the promises variant at the moment of instantiating the client — that is, if you’re writing
new Ably.Rest.Promise(…)
ornew Ably.Realtime.Promise(…)
— then update your code to remove the use of thePromise
property, i.e. writenew Ably.Realtime(…)
ornew Ably.Rest(…)
.
v2 of the library drops support for some platforms that v1 supported. Most notably, it no longer supports Internet Explorer or Node.js versions below 16. For more information, see this section of the Readme.
Note: This change is most likely to affect you if you’re already explicitly configuring your firewall (or instructing your users to configure their firewall) to allow realtime connections to Ably, as described in this FAQ on ably.com.
When attempting to establish a realtime connection using WebSocket, v2 uses the wss://ws-up.ably-realtime.com
endpoint to check if WebSocket connectivity is available. Update your firewall whitelist to allow connectivity to that endpoint.
In v1 of ably-js, realtime clients with multiple available transports would initially attempt to connect with the transport most likely to succeed (xhr_polling
in web browsers). Upon the success of this initial connection, they would subsequently attempt to "upgrade" to a preferable transport such as WebSocket.
This behaviour has been changed in v2. Now, realtime clients will instead first attempt to connect to Ably using WebSocket, and only failover to alternative transports if this WebSocket connection attempt fails. For the vast majority of users this will result in a smoother initial connection sequence, however in environments where WebSocket connections are unavailable and time out instead of failing immediately (such as when using a corporate proxy which strips WebSocket headers) this may result in a slower initial connection. Once a connection is first established, transport preference will be cached in the web browser local storage so subsequent connections will use the best available transport. If you expect WebSocket connection attempts to always fail in your enviornment, you can skip the WebSocket connection step by explicitly providing a list of transports which omits the web_socket
transport via the transports
client option.
Note: This section is only relevant if you’re using the Crypto.generateRandomKey()
method.
If you’re using the Crypto.generateRandomKey()
method, you’ll need to change how you call this method. In v1, this method required that you pass a callback. In v2, it communicates its result by returning a promise.
So you need to change code that looks like this:
Ably.Realtime.Crypto.generateRandomKey((err, key) => { … })
into code that makes use of the returned promise, for example by using the await
keyword on it:
const key = await Ably.Realtime.Crypto.generateRandomKey();
Note: This section is only relevant if you’re using the Rest.request()
or Realtime.request()
methods; that is, if you’re using the library to manually make a request to the Ably REST API.
The signature of this method has been changed; it now requires that you pass a version
argument to specify the version of the REST API to use. For compatibility with v1 of this library, specify a version of 2
.
As an example, given the current code to get the service time:
const time = await rest.request('get', '/time');
add an argument to specify the API version:
const time = await rest.request('get', '/time', 2);
Note: This section is only relevant if you’re using the Rest.stats()
or Realtime.stats()
method; that is, if you’re using the library to retrieve your application’s usage statistics.
The Stats
type returned by the Rest.stats()
and Realtime.stats()
method has been simplified in v2. Specifically, there is a new property called entries
, which is an object all of whose properties have numeric values. The entries
property replaces the following properties:
all
inbound
outbound
persisted
connections
channels
apiRequests
tokenRequests
xchgProducer
xchgConsumer
pushStats
processed
You should migrate your code to use the entries
property instead of these properties.
The main differences between the v1 Stats
type and the entries
property in v2 are:
- the various stats that count messages in different ways (
all
,inbound
,outbound
,persisted
,processed
) are now undermessages
instead of all in the top level connections
is no longer broken down intoplain
andtls
- instead of top-level
apiRequests
andtokenRequests
, which doesn't make much sense because token requests are API requests, you now have a top levelapiRequests
that’s broken down currently intotokenRequests
andother
with anall
aggregate, with potential to split other out into more specific types later push
is completely reorganised in a way that makes more sense
For more detailed information on entries
, see the Stats
type’s new schema
property. It provides you with the URL of a JSON Schema which describes the structure of this Stats
object. (Alternatively, if you wish to view this schema now, you can find it here.)
As an example, given the following v1 code that uses the stats()
API:
const stats = await rest.stats();
const inboundMessageCount = stats[0].inbound.all.messages.count;
This is the equivalent v2 code:
const stats = await rest.stats();
const inboundMessageCount = stats[0].entries['messages.inbound.all.messages.count'] ?? 0;
Notice that a given property may be absent from entries
. If a property is absent, this is equivalent to its value being 0.
Note: This section is only relevant if you’re using the RealtimeChannel.whenState()
or Connection.whenState()
methods.
The RealtimeChannel.whenState()
and Connection.whenState()
methods now return null
when the connection is already in the given state, instead of attempting to synthesize an artificial state change.
Note: This section is only relevant if you’re using Message
or PresenceMessage
’s fromEncoded
or fromEncodedArray
methods.
Message
and PresenceMessage
’s fromEncoded
and fromEncodedArray
methods now operate asynchronously. That is, instead of returning the decoding result, they return a promise. Update your code to retrieve the result of these promises, for example by using the await
keyword.
Note: This section is only relevant if you’re using the cipher
client option and running in a browser.
If you’re making use of the cipher
client option to enable symmetric encryption on a channel, be aware that when running in a browser this functionality is now implemented using the Web Crypto API. Hence, this functionality is only available when this API is available; namely, when the current environment is a secure context. Roughly speaking, this means that you can now only use Ably channel encryption inside a web page when serving your page over HTTPS or from localhost
.
Note: This section is only relevant if you’re using the cipher
client option and running in a browser.
When running in a browser, the library previously used the CryptoJS library for implementing symmetric encryption on a channel. As mentioned here, the library now instead uses the built-in Web Crypto API.
This means that the library is no longer capable of interacting with CryptoJS’s WordArray
type. Specifically:
Crypto.generateRandomKey()
now returns anArrayBuffer
instead of aWordArray
.Crypto.getDefaultParams({ key })
no longer accepts aWordArray
key; pass anArrayBuffer
orUint8Array
instead.
Note: This section is only relevant if you’re explicitly writing "xhr_streaming"
or "xhr"
as part of your transports
client option.
v1 offered the xhr_streaming
transport, also known as xhr
, primarily to provide a performant realtime transport for browsers which did not support WebSocket connections. Since this limitation does not apply to any of the browsers supported by v2, the xhr_streaming
transport has been removed. If your transports
client option contains "xhr_streaming"
or "xhr"
, remove this value. If you still wish to explicitly request a non-WebSocket transport, request xhr_polling
instead.
Note: This section is only relevant if you’re explicitly writing "jsonp"
as part of your transports
client option.
v1 offered JSONP as a fallback transport for browsers that did not support cross-origin XHR. Since this limitation does not apply to any of the browsers supported by v2, the JSONP transport has been removed. If your transports
client option contains "jsonp"
, remove this value.
Note: This section is only relevant if you’re importing ably/build/ably.noencryption.min.js
or using the ably.noencryption.min-1.js
CDN build.
In v1, we provided a separate version of the library that did not support the cipher
channel option. This was offered for those who did not wish to bloat their app’s bundle size with encryption code.
Since v2 no longer uses the CryptoJS library, the cipher
channel option functionality now has a much smaller impact on your app’s bundle size. So, we no longer offer the noencryption
variant of the library.
Furthermore, v2 introduces the modular variant of the library, which is specifically aimed at those who are concerned about their app’s bundle size. It provides a general mechanism for choosing which Ably functionality you wish to include in your app’s bundle. So, if you do not wish to incur even the small bundle size overhead that the cipher
channel option imposes, consider using the modular variant of the library without the Crypto
module.
Note: This section is only relevant if you’re using ably-js inside a Web Worker.
In v1, if you wished to use ably-js inside a Web Worker, you had to use a special build of the library, by writing import Ably from 'ably/build/ably-webworker.min'
.
In v2, the library can be used inside a Web Worker without needing to use a special build, and this special ably-webworker
build no longer exists. So, update your code to import Ably from 'ably'
.
Note: This section is only relevant if you’re importing ably/build/ably-commonjs.js
or ably/build/ably-commonjs.noencryption.js
.
The ably/build/ably-commonjs.js
file no longer exists.
- If you’re currently writing
require('ably/build/ably-commonjs.js')
, writerequire('ably')
instead. - If you’re currently writing
import * as Ably from 'ably/build/ably-commonjs.js'
, writeimport * as Ably from 'ably'
instead.
(The same applies for ably-commonjs.noencryption.js
.)
v1 exported many of its types inside the Types
TypeScript namespace. v2 removes this namespace, and the types it contained are now exported at the top level. Remove your references to the Types
namespace.
For example, code like this:
let options: Ably.Types.ClientOptions = { key: 'foo' };
should now be written
let options: Ably.ClientOptions = { key: 'foo' };
In order to avoid a clash with the existing top-level types of the same type, the Types.Rest
and Types.Realtime
types have been renamed to RestClient
and RealtimeClient
. If you’re referring to these types in your code (which is probably unlikely) then update these references.
The v1 type declarations contained various sets of three related types in order to reflect the different asynchronicity styles offered by the SDK. For example, it had a RealtimeChannelBase
type which contained functionality common to the callbacks-based and promises-based API of a realtime channel, and then RealtimeChannelCallbacks
and RealtimeChannelPromises
types which contained the APIs unique to those asynchronicity styles.
Now that the callbacks-based variant of the library has been removed, we’ve simplified the types, removing the *Callbacks
types and combining each pair of *Base
and *Promise
types into a single type. For example, where previously there existed three types that described a realtime channel, now there is just one, named RealtimeChannel
. You should update your code to stop referring to the suffixed types. In general, this is a case of removing these suffixes from the type names.
The following properties of Message
are now declared as optional:
clientId
data
encoding
extras
name
This update to the type declarations reflects the fact that these properties are not (and never have been, despite what the v1 declarations suggested) guaranteed to be populated on a message received from Ably.
The Message
type has been changed so that it represents a message that you publish to Ably (see migration guidance for the changes to the type declaration for the publish()
method). Since you are not required to populate the following properties of a message that you publish to Ably, they are now optional:
id
timestamp
If you were previously using the Message
type to refer to a message received from Ably (e.g. from the RealtimeChannel.subscribe()
or RealtimeChannel.history()
methods), then switch to using the new InboundMessage
type, which represents a message received from Ably; it’s a version of Message
in which these two properties are not optional (matching the v1 Message
type).
In v1, the type declarations for the publishing methods Channel.publish()
and RealtimeChannel.publish()
stated that they accepted an argument of type any
. This was inaccurate, as in fact there were expectations about the shape of the message or messages that you pass to these methods. Now, the type declarations state you must pass an object that satisfies the Message
interface, or an array of such objects.
Note: It’s unlikely that you’re affected by this change.
In v1, there existed a pattern in the type declarations where a single name was used for a namespace and also a type whose possible values were the members of that namespace. For example:
declare namespace ChannelState {
type INITIALIZED = 'initialized';
type ATTACHING = 'attaching';
type ATTACHED = 'attached';
type DETACHING = 'detaching';
type DETACHED = 'detached';
type SUSPENDED = 'suspended';
}
export type ChannelState =
| ChannelState.FAILED
| ChannelState.INITIALIZED
| ChannelState.SUSPENDED
| ChannelState.ATTACHED
| ChannelState.ATTACHING
| ChannelState.DETACHED
| ChannelState.DETACHING;
In v2, the namespace has been changed so that its name has a plural inflection. For example, whilst the ChannelState
type maintains its name, the ChannelState
namespace is now called ChannelStates
. Update your code accordingly.
Also, there was a type in v1 called ChannelModes
which was just an alias for Array<ChannelMode>
. In order to accommodate the naming scheme described above, the ChannelModes
type no longer has its v1 meaning.
In v1, the Types.RealtimeBase
class is declared as inheriting from the Types.RestBase
class. As mentioned above, these classes no longer exist. Their replacements (the RealtimeClient
and RestClient
interfaces) do not declare an inheritance relationship.
Note: This section only applies if you’re using the Connection.recoveryKey
property. You’re likely only using this if making use of the library’s connection recovery functionality, and have opted out of the library’s recovery key persistance functionality; that is, if you’re populating the recover
client option with a string (as opposed to a callback).
The Connection.recoveryKey
property is deprecated and will be removed in a future version. It has been replaced by the Connection.createRecoveryKey()
method. The return value of this method is identical to the value of the Connection.recoveryKey
property. Update your code to use this return value.
Aimed at those who are concerned about their app’s bundle size, the modular variant of the library allows you to create a client which has only the functionality that you choose. Unused functionality can then be tree-shaken by your module bundler.
To get started with the modular variant of the library, see this section of the Readme.