centrifuge-js

repository·master·Indexed 19 days ago

https://github.com/centrifugal/centrifuge-js

JavaScript client SDK for bidirectional communication with Centrifugo and Centrifuge-based servers. Supports real-time messaging via WebSockets with fallbacks like SSE and HTTP-streaming across browser, Node.js, and React Native environments. Features include channel subscriptions, RPC, presence tracking, and history retrieval.

Tokens
19.8K
Snippets
57
Records
94
Agent score
68%

What's inside centrifuge-js

  1. Narrow subscription types using the `type` property

    master

    In the next major version, client.getSubscription(channel) returns an AnySubscription (a union of StreamSubscription, MapSubscription, or SharedPollSubscription). To access methods specific to a subscription type (like publish on a MapSubscription), you must use a type guard on the type property.

    Each subscription class has a readonly type property with a string literal value:

    • stream for StreamSubscription
    • map for MapSubscription
    • shared_poll for SharedPollSubscription
    const sub = client.getSubscription('my-channel');
    
    if (sub?.type === 'map') {
      // TypeScript now knows 'sub' is a MapSubscription
      sub.publish('key', data);
    } else if (sub?.type === 'shared_poll') {
      // TypeScript now knows 'sub' is a SharedPollSubscription
      sub.track(['key1', 'key2']);
    }
  2. Use Map subscriptions (experimental)

    master

    Map subscriptions deliver a real-time key-value collection managed by Centrifugo. Clients receive a full snapshot on subscription and observe live updates to individual keys.

    Key Features:

    • Events: Emits a sync event when a fresh snapshot is delivered (initial subscribe or unrecoverable recovery). Emits update events for individual key changes (set/remove).
    • Variants: newMapClientsSubscription and newMapUsersSubscription provide presence-style maps backed by $clients:* and $users:* channels.
    • Mutation: Use publish(key, data) and remove(key) to change values.
    WARNING

    Experimental: Requires Centrifugo >= v6.8.0. Behavior may change in future minor releases.

    const mapSub = centrifuge.newMapSubscription('my_map_channel');
    
    mapSub.on('sync', (snapshot) => {
      // Handle full snapshot
    });
    
    mapSub.on('update', (update) => {
      // Handle individual key change
    });
    
    mapSub.publish('key1', { value: 'data' });
    mapSub.remove('key1');
  3. Stream subscriptions with `getState`

    master

    For positioned/recoverable stream channels, you can use the getState pattern to ensure data consistency. This allows the SDK to load the application's current state (from a database or API) and subscribe from that specific position.

    How it works:

    1. The SDK invokes your getState callback during the initial subscription or when a server recovery fails (error 112).
    2. Inside the callback, you must read the stream position first, then read your data to ensure the position acts as a lower bound.
    3. Centrifugo delivers only change events; your database remains the source of truth.

    Important:

    • Requires Centrifugo >= v6.8.0.
    • Recovered publications may overlap with data loaded in getState. Ensure your updates are idempotent or deduplicate using the offset.
    • On successful recovery, getState is not called.
  4. Use Shared poll subscriptions (experimental)

    master

    Shared poll subscriptions move the polling logic from the client to Centrifugo. Centrifugo polls the backend at a configurable interval and fans out changes to all interested clients, reducing backend load.

    Key Features:

    • Management: Use track(keys) and untrack(keys) on the returned SharedPollSubscription to manage which items the client is interested in.
    • Authorization: Provide a getSignature callback in the options to authorize tracked keys via HMAC signatures.
    • Events: Emits an update event per item when the backend reports a new version. Includes removed: true events when a key is revoked.
    WARNING

    Experimental: Requires Centrifugo >= v6.8.0. Behavior may change in future minor releases.

    const pollSub = centrifuge.newSharedPollSubscription('poll_channel', {
      getSignature: (key) => 'your_hmac_signature'
    });
    
    pollSub.on('update', (update) => {
      // Handle update or removal
    });
    
    pollSub.track(['key1', 'key2']);
    pollSub.untrack(['key1']);
  5. Configure multiple real-time transports and fallbacks

    master

    While WebSocket is the primary transport, you can provide an array of TransportEndpoint objects to enable fallbacks (like HTTP-streaming or SSE) in environments where WebSockets are blocked by firewalls or proxies.

    The client will attempt the transports in the order provided during the initial handshake. Once a transport succeeds, it will be used for subsequent reconnects.

    Supported transport types:

    • websocket
    • http_stream (requires server-side bidirectional emulation)
    • sse (requires server-side bidirectional emulation)
    • sockjs (Deprecated: removed in Centrifugo v6 and will be removed in centrifuge-js v6)
    • webtransport (Experimental; currently only supported by Centrifugo)
    const transports = [
        {
            transport: 'websocket',
            endpoint: 'ws://example.com/connection/websocket'
        },
        {
            transport: 'http_stream',
            endpoint: 'http://example.com/connection/http_stream'
        },
        {
            transport: 'sse',
            endpoint: 'http://example.com/connection/sse'
        }
    ];
    const centrifuge = new Centrifuge(transports);
    centrifuge.connect();
  6. Implement token authentication with getToken

    master

    To handle dynamic authentication or token refreshing, provide a getToken function in the Centrifuge options. The SDK will call this function when it needs a token (e.g., during initial connection or when the current token expires).

    Key Behaviors:

    • If token is not provided but getToken is, the SDK assumes token authentication is required and will attempt to fetch a token before the initial connection.
    • If your getToken function throws a Centrifuge.UnauthorizedError(), the client will move to a disconnected state (useful for logout/permission loss).
    • Any other error thrown will cause the SDK to retry the token refresh after a jittered delay.

    Note: The connection token must be generated on your application backend.

    import { Centrifuge, UnauthorizedError } from 'centrifuge';
    
    async function getToken() {
        if (!loggedIn) {
            // Throwing UnauthorizedError disconnects the client
            throw new UnauthorizedError();
        }
        const res = await fetch('/centrifuge/connection_token');
        if (!res.ok) {
            throw new Error(`Unexpected status code ${res.status}`);
        }
        const data = await res.json();
        return data.token;
    }
    
    const client = new Centrifuge(
        'ws://localhost:8000/connection/websocket',
        {
            getToken: getToken
        }
    );
  7. Handle Server-side subscriptions

    master

    Server-side subscriptions are created by the server upon connection establishment. While the client has less control over them than client-side subscriptions, the SDK maintains them in an internal registry and provides event hooks.

    Available Events for Server-side Subscriptions:

    • subscribed: Called when the client moves to a connected state or receives a Subscribe push.
    • subscribing: Called during reconnection or explicit disconnection.
    • unsubscribed: Called when the server sends an unsubscribe push or the subscription disappears upon reconnection.
    • publication: Called when the server sends a Publication over the channel.

    Top-level methods for server-side channels:

    • publish(channel, data)
    • history(channel, options)
    • presence(channel)
    • presenceStats(channel)
    const client = new Centrifuge('ws://localhost:8000/connection/websocket', {});
    
    client.on('subscribed', (ctx) => {
        console.log('subscribed to server-side channel', ctx.channel);
    });
    
    client.on('publication', (ctx) => {
        console.log('publication receive from server-side channel', ctx.channel, ctx.data);
    });
    
    client.connect();
  8. Use Protobuf protocol

    master

    To use the Protobuf protocol (which uses protobuf.js under the hood), you must import the specific Protobuf build of the client. This allows you to send and receive binary data as Uint8Array.

    Installation:

    HTML:

    <script src="https://unpkg.com/centrifuge@5.0.0/dist/centrifuge.protobuf.js"></script>

    NPM:

    import { Centrifuge } from 'centrifuge/build/protobuf';

    Usage Note: When using the Protobuf client, you cannot send JSON-like objects directly. You must encode your data into a Uint8Array before calling publish.

    import { Centrifuge } from 'centrifuge/build/protobuf';
    
    const data = new TextEncoder("utf-8").encode(JSON.stringify({"any": "data"})); 
    sub.publish(data);
  9. Install centrifuge-js via npm or CDN

    master

    You can install the SDK using npm for Node.js or browser-based bundlers:

    npm install centrifuge

    Then import the Centrifuge class in your project:

    import { Centrifuge } from 'centrifuge';

    For direct browser usage without a bundler, you can use the unpkg CDN. Note that browser builds target ES6:

    <script src="https://unpkg.com/centrifuge@5.0.0/dist/centrifuge.js"></script>

    Note: Replace 5.0.0 with the specific version you require.

  10. Configure Subscription tokens and getToken()

    master

    You can provide a subscription token (typically a JWT generated on your backend) when creating a subscription. If the token includes an expiration, the SDK can automatically refresh it using the getToken option.

    How getToken works:

    1. The SDK calls your provided getToken function when a new token is needed.
    2. Your function must return the new token string.
    3. If your function returns an empty string, the SDK assumes the user no longer has permission and will unsubscribe the client.
    4. If your function throws an error, the SDK will retry the refresh after a jittered delay.

    Note: If you provide getToken but no initial token, the SDK assumes you are using token authorization and will attempt to fetch a token before the initial subscription attempt.

    import { Centrifuge, UnauthorizedError } from 'centrifuge';
    
    async function getToken(ctx) {
        // ctx contains the channel name
        const res = await fetch('/centrifuge/subscription_token', {
            method: 'POST',
            headers: new Headers({ 'Content-Type': 'application/json' }),
            body: JSON.stringify(ctx)
        });
    
        if (!res.ok) {
            if (res.status === 403) {
                // Throwing UnauthorizedError stops further refresh attempts and unsubscribes
                throw new UnauthorizedError();
            }
            throw new Error(`Unexpected status code ${res.status}`);
        }
    
        const data = await res.json();
        return data.token;
    }
    
    const client = new Centrifuge('ws://localhost:8000/connection/websocket', {});
    
    const sub = client.newSubscription('news', {
        token: 'JWT-GENERATED-ON-BACKEND-SIDE',
        getToken: getToken,
    });
    
    sub.subscribe();
  11. Use Centrifuge in NodeJS

    master

    Since NodeJS does not have a native WebSocket implementation in its standard library, you must explicitly provide a WebSocket constructor (e.g., ws).

    Option 1: Explicitly pass the WebSocket object

    import { Centrifuge } from 'centrifuge';
    import WebSocket from 'ws';
    
    var centrifuge = new Centrifuge('ws://localhost:8000/connection/websocket', {
        websocket: WebSocket
    });

    Option 2: Define WebSocket globally

    import { Centrifuge } from 'centrifuge';
    import WebSocket from 'ws';
    
    global.WebSocket = WebSocket;
    
    const centrifuge = new Centrifuge('ws://localhost:8000/connection/websocket');
  12. Pass a custom WebSocket constructor for custom headers

    master

    In non-browser environments, you can wrap a WebSocket constructor to inject custom headers (like Authorization) during connection initialization.

    Implementation Pattern:

    1. Create a wrapper function that returns a class extending WebSocket.
    2. In the constructor, intercept the arguments to inject your custom options/headers.
    3. Pass this wrapper to the websocket option in the Centrifuge constructor.
    const myWs = function (options) {
      return class wsClass extends WebSocket {
        constructor(...args) {
          if (args.length === 1) {
            super(...[...args, 'centrifuge-json', ...[options]])
          } else {
            super(...[...args, ...[options]])
          }
        }
      }
    }
    
    var centrifuge = new Centrifuge('ws://localhost:8000/connection/websocket', {
        websocket: myWs({ headers: { Authorization: '<token or key>' } }),
    });