Ably JavaScript SDK

repository·main·Indexed 18 days ago

https://github.com/ably/ably-js

Realtime client library for Ably, providing a Pub/Sub SDK for building messaging, presence, and state synchronization experiences. Supports ES2017, Node.js, React (>=16.8.x), and TypeScript. Features include a standard Realtime client, a tree-shakable modular variant for smaller bundle sizes, and a REST client. Provides comprehensive tools for managing channel and connection states, presence tracking, and flexible authentication via API keys, Auth URLs, or callbacks.

Tokens
41.3K
Snippets
114
Records
181
Agent score
62%

What's inside ably-js

  1. Overview of ably-js React Hooks

    main

    The ably-js React Hooks package provides an idiomatic way to integrate Ably into React applications. It manages the lifecycle of Ably SDK instances, automatically handling subscriptions and unsubscriptions to channels and events during component re-renders.

    Key capabilities include:

    • Interacting with Ably channels via React Hooks.
    • Publishing messages using functions provided by the hooks.
    • Managing and receiving user presence notifications on channels.
    • Sending presence updates.
  2. Understand the difference between the Realtime and REST interfaces

    main

    Ably provides two distinct interfaces for interacting with its services:

    • Realtime interface: Maintains a persistent connection to Ably. It is used for low-latency operations such as publishing messages, subscribing to channels, and managing presence.
    • REST interface: A stateless interface typically implemented on the server side. It is used for administrative or one-off tasks such as retrieving statistics, performing token authentication, and publishing to a channel without maintaining a persistent connection.
  3. Use PathObject for navigating LiveObjects

    main

    In v2.16+, the API entrypoint has changed from channel.objects to channel.object. Instead of getRoot(), use channel.object.get() to obtain a PathObject.

    PathObject operations resolve the path at runtime. This means:

    • Obtaining a PathObject never fails, even if the path doesn't exist yet.
    • Access methods (like .value() or .entries()) return empty defaults (e.g., undefined or an empty iterator) if the path is invalid at runtime.
    • Mutation methods (like .set() or .increment()) will throw errors if the path does not resolve to the expected type at runtime.

    You can navigate nested structures by chaining .get() calls or using .at('path.to.key').

    // Navigate nested structures
    const shape = myObject.get('shape');
    const colour = myObject.get('shape').get('colour');
    
    // Use .at() for fully-qualified paths
    const border = myObject.at('shape.colour.border');
    
    // Get the string path of a location
    const path = myObject.get('shape').get('colour').get('border').path();
  4. Define Ably channels with ChannelProvider

    main

    Use the ChannelProvider component to define a specific channel for a subtree of your application. This allows child components to use channel-based hooks without explicitly passing the channel name.

    Channel Resolution:

    • If a hook (like useChannel) is called without a channelName, it resolves to the nearest enclosing ChannelProvider.
    • When providers are nested, the nearest one wins.
    • Note on ablyId: Channel resolution is scoped by ablyId. If a ChannelProvider uses a non-default ablyId, you must pass that same ablyId to the hook to resolve the channel correctly.
    • SSR Warning: React hooks are designed for client-side use. Ensure components using these hooks are only rendered on the client side during Server-Side Rendering.
    <ChannelProvider channelName="your-channel-name">
      <Component />
    </ChannelProvider>
    
    // Inside Component, the name is inferred:
    const { channel } = useChannel((message) => {
      console.log(message);
    });
  5. Choose between the Default and Modular Ably SDK variants

    main

    The Ably JavaScript Client Library SDK offers two distinct variants depending on your application's requirements:

    1. Default variant: Always creates a fully-featured Ably client. This is the standard choice for most applications.
    2. Modular (tree-shakable) variant: Designed for developers concerned about bundle size. This variant allows you to selectively include only the specific functionality you need, enabling tree-shaking to reduce the final application footprint.
  6. Implicit channel attach on `object.get()`

    main

    Starting with version 2.16, calling channel.object.get() performs an implicit channel attach. You no longer need to explicitly call await channel.attach() before accessing objects. This prevents scenarios where forgetting to attach causes channel.objects.getRoot() to hang indefinitely.

    // No explicit attach needed - .get() handles it automatically
    const myObject = await channel.object.get();
    // The channel is automatically attached and synced
  7. Understand the PathObject concept in LiveObjects

    main

    In ably-js v2.16 and later, the LiveObjects API shifted from an instance-based model to a path-based model using PathObject.

    Instead of interacting directly with specific LiveMap or LiveCounter instances, you now interact with a PathObject, which represents a path to a location within your channel's object hierarchy.

    Key differences and benefits:

    • Runtime Resolution: Operations are evaluated against the current value at a path at the moment the operation is invoked, rather than when the object was created. This makes code more resilient to changes in the object structure.
    • Resilient Subscriptions: You subscribe to paths rather than specific object instances. This prevents subscriptions from breaking if an object at a specific path is replaced with a new instance.
    • Simplified Hierarchy: You can create deeply nested structures in a single operation without manually managing child objects or worrying about orphaned instances.
    • Null Safety: The path-based approach removes the need for explicit null checks when traversing hierarchies, as the path resolves dynamically.
  8. Use the modular variant for smaller bundle sizes

    main

    The modular variant allows you to build a tree-shakable client by importing only the necessary functionality. This is ideal for optimizing bundle size in web applications.

    To use it, import BaseRealtime and specific plugins from ably/modular. You must provide at least one HTTP request implementation (FetchRequest or XHRRequest) and at least one realtime transport implementation (WebSocketTransport or XHRPolling).

    Note: The modular variant performs less logging than the default variant to further reduce size. It only logs errors (logLevel 1) and select network events.

    import { BaseRealtime, WebSocketTransport, FetchRequest, RealtimePresence } from 'ably/modular';
    
    const client = new BaseRealtime({
      key: 'YOUR_ABLY_API_KEY', // Replace with a real key from the Ably dashboard
      plugins: {
        WebSocketTransport,
        FetchRequest,
        RealtimePresence,
      },
    });
  9. Replace `configureAbly` with `AblyProvider`

    main

    In Ably 1.x and later, the global configureAbly function has been replaced by the AblyProvider context provider. This change improves compatibility with hot module reloading (HMR).

    To use the hooks, you must wrap your component tree (ideally at a high level) with the AblyProvider and pass your Ably.Realtime instance to the client prop.

    Standard implementation:

    const client = new Ably.Realtime(options);
    
    return <AblyProvider client={client}>{children}</AblyProvider>;
  10. Split `usePresence` into `usePresence` and `usePresenceListener` in v2

    main

    In ably-js v2, the single usePresence hook has been split to separate presence state management from presence data listening:

    1. usePresence: Use this to enter presence with an initial state and to update the current client's status. It no longer returns presenceData or accepts an onPresenceUpdated callback.
    2. usePresenceListener: Use this to listen for presence updates. It returns the presenceData object and accepts an onPresenceMessageReceived callback which is triggered on new presence messages.
    // v2 Pattern: Separate management and listening
    const { updateStatus } = usePresence({ channelName: 'presence-channel-name' }, { foo: 'bar' });
    
    const { presenceData } = usePresenceListener({ channelName: 'presence-channel-name' }, (update) => {
      console.log(update);
    });
  11. Switch from Callbacks to Promises in v1

    main

    Before upgrading to v2, if you are using the callbacks variant of v1, you must switch to the promise-based variant.

    1. Change the import/instantiation:

    • require('ably/callbacks') $\rightarrow$ require('ably/promises')
    • new Ably.Realtime.Callbacks(...) $\rightarrow$ new Ably.Realtime.Promise(...)

    2. Update method calls: Methods that previously took a callback (err, result) as the last argument should now be called using await or .then()/.catch().

    // v1 Callbacks style
    channel.history({ direction: 'forwards' }, (err, paginatedResult) => {
      if (err) return;
      // use paginatedResult
    });
    
    // v1 Promises style (Required before v2 upgrade)
    // Option A: async/await
    try {
      const paginatedResult = await channel.history({ direction: 'forwards' });
    } catch (err) {
      // handle error
    }
    
    // Option B: .then()
    channel.history({ direction: 'forwards' })
      .then((paginatedResult) => { /* use result */ })
      .catch((err) => { /* handle error */ });

    Note: In v1, Crypto.generateRandomKey() is an exception; it remains callback-based even in the v1 promise variant. In v2, it becomes promise-based.

    // v1 Callbacks style
    channel.history({ direction: 'forwards' }, (err, paginatedResult) => {
      if (err) {
        // Perform some sort of error handling
        return;
      }
    
      // Make use of paginatedResult
    });
    
    // v1 Promises style (Required before v2 upgrade)
    // Option A: async/await
    try {
      const paginatedResult = await channel.history({ direction: 'forwards' });
      // Make use of paginatedResult
    } catch (err) {
      // Perform some sort of error handling
    }
    
    // Option B: .then()
    channel
      .history({ direction: 'forwards' })
      .then((paginatedResult) => {
        // Make use of paginatedResult
      })
      .catch((err) => {
        // Perform some sort of error handling
      });
  12. Control hook mounting with the `skip` parameter

    main

    The skip parameter (a boolean) allows you to prevent hooks from automatically attaching to a channel upon component mount. This is useful for:

    1. Asynchronous Authentication: Preventing errors when a user is not yet authorized.
    2. Conditional Feature Access: Only connecting to premium channels if the user has the required privileges.

    When skip is true, the hook does nothing. When it changes to false, the hook will attempt to attach to the channel.

    const [isAuth, setIsAuth] = useState(false);
    
    // The hook will only attempt to connect once isAuth is true
    useChannel({ channelName: 'chat', skip: !isAuth }, (message) => {
      console.log(message);
    });