Skip to content

Instantly share code, notes, and snippets.

@shodgetts
Last active December 9, 2020 19:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shodgetts/fffe374e2a931b3ea095d5e456f8403b to your computer and use it in GitHub Desktop.
Save shodgetts/fffe374e2a931b3ea095d5e456f8403b to your computer and use it in GitHub Desktop.

Best Practices

Instantiating Stream Client and having a Single WebSocket Connection Per User

In every client side integration it is going to be necessary to open a WebSocket connection between the client (cell phone/laptop/tablet, etc) and the API. In every implementation there will be a setUser(); (or SDK equivalent). A few key points regarding this endpoint.

  • This method performs multiple functions
  • The Primary purpose of this method is to open a WebSocket connection. Thus, it should only be used once per session per user. Opening multiple WebSockets per user opens your application up to a myriad of problems, including performance, billing, and unexpected behavior.
  • It Gets or Creates a new user. It will increase your MAU (monthly active users), Conccurent Connections (as long as the WebSocket is active) and it not a valid method for importing users.
  • It should never be used server-side. Some SDK’s that can be used both server-side and client-side (JS SDK) expose this method in both places, so this is possible and strongly unadvised.
  • The WebSocket can be closed using client.disconnect(); (or SDK equivalent).
  • The API response from this method contains useful information (more of this later).

React useEffect example

// ❌ This is super wrong, because the following code will keep on creating new websocket connection
// everytime useEffect hook gets executed (on chances to `currentUser`)
const [ chatClient, setChatClient ] = useState(null);
const [ currentUser, setCurrentUser ] = useState(null);
useEffect(() => {
    const initChat = async () => {
        const client = new StreamChat('api_key', 'secret');
        const result = await chatClient.setUser({ id: currentUser });
        setChatClient(client);
    }

    initChat();
    // .. and some more code here
}, [currentUser])


// ✅ You should disconnect once you are done with chatClient
const [ chatClient, setChatClient ] = useState(null);
const [ currentUser, setCurrentUser ] = useState(null);
useEffect(() => {
    const initChat = async () => {
        const client = new StreamChat('api_key', 'secret');
        const result = await chatClient.setUser({ id: currentUser });
        setChatClient(client);
    }

    initChat();
    // .. and some more code here

    // This is correct teardown logic to ensure the WebSocket connection does not remain open once the component is 
    unmounted or initChat is called again.
    return () => {
        chatClient.disconnect();
    }
}, [currentUser])

Ideas for best practices

  • Search your codebase for the setUser(); method (or SDK equivalent) and ensure it is only used once.
  • Initiate the Chat Client and used the setUser(); method at the highest level of your application and reuse that instance across the codebase.
  • Check the network tab of your browser to ensure only 1 WebSocket connection exists at a time.
  • Get in touch with support@getstream.io if you are still unsure about this topic.

Create your Application in the correct Region

The Stream Chat API is hosted in 4 publicly available locations

  • US East
  • EU West
  • Singapore
  • Tokyo

When creating an App we advise that it is created in the region closest to where the majority of the users will be connecting from. We are unable to migrate existing applications.

The Stream SDK’s are going to default to the US East location. You can change the URL of API calls by following these instructions.

Querying Channels

It is going to be necessary in most applications to query the API for channels. Watching channels is the method by which you can receive realtime updates on a channel with the Stream Chat API.

Here are some tips:

  • A Channel is not created until one of the following methods is used on the channel instance, each method is subtly different.
channel.create();
channel.query();
channel.watch();

Only one of these is necessary. For example, it is redundant to create and watch the same Channel. watch is also doing the work of creating the channel in addition to watching the Channel for real time updates.

  • With queryChannels, a client can watch up to 25 Channels in a single API call, meaning there is no need to watch them individually or use channel.watch(); after queryChannels. This will substantially increase API calls and reduce performance.

queryChannels filters

A Channel list in an application can often form the backbone of the chat experience, and be one of the first views that a user sees upon opening a chat experience. Although not entirely pertinent for a livestream situation, it is important to run a filter that is optimal if it is needed.

A good guide is to use a filter that is as selective as possible. For example, if your filter is still going to return >50% of channels in the application, it may be problematic.

  • Avoid NOR logical operators and NIN and NOT comparison operators in the filter variable as they are not selective.
  • Filter by CID is about the most performant query you can use.
  • For a social messaging (think DM’s, group chats, etc), a minus filter of type and member should be used.
// Filter by type and member (use case: social messaging
const filter = { type: "messaging", members: {$in: userID} }
// Filter by CID
const filter = { cid: channelCID };
  • Avoid overly complex queries that have >1 AND or OR statement.

  • Use custom fields on channels to make filters more simple.

  • Reach out to support@getstream.io if you plan on having millions of channels and are unsure of your queryChannels filter.

  • Filtering by type + channel_id is not recommended. Instead use CID.

  • Filtering by type alone is not recommended. Instead use CID.

// Not recommended
const filter = { type: "messaging" };
// Instead, use 
const filter = { type: "messaging", members: {$in: userID} }
// Not recommended. For example, channel_id = "abChannel11237"
const filter = { id: { $in: [channel_id] } };
// Instead, use channel_cid. For example, channel_cid = "messaging:abChannel11237"
const filter = { cid: { $in: [channel_cid] } };

queryChannels sort

  • It is always best to use a sort parameter in the query, although we will default to to updated_at. This is the maximum value between channel.create_at and channel.last_message_at.
  • The other sort parameter that is highly optimised (indexed) is last_message_at.
const sort = { last_message_at: -1 }
  • Sorting on custom fields is possible but it does have some scalability implications. Sorting on a custom field is going to be up to ~2000-3000 channels. However, for Enterprise customers, this is possible for us to provide optimizations here in certain circumstances.

Rate Limits

Rate limits exist to protect both our API and your integration and to avoid costly bills at the end of the month due to mistakes.

  • If you are being rate limited on the Standard Plan, it is often a sign of an integration error or an unoptimized integration.
  • Make use of batch endpoints.
  • Cache responses and store locally as necessary.
  • Listen to realtime events and update locally stored variables rather than querying the API again and again.

The rate limits given in the Standard plan are almost always sufficient for the user volume that is allocated to the plan. However, if you plan on exceeding this usage then please get in touch with support@getstream.io with your appID and we will allocate additional resources as necessary.

Adding Users to the Application and Channels

A common bottleneck for API calls is both adding users to the application and adding users to channels. If possible, it can be beneficial to add users in batches before events start. Adding users using the setUser(); method is not recommended and is cumbersome in comparison to upsertUsers([…100 users here]);

Application Settings

There are two main areas of settings where mistakes can be made - permission settings, and channel feature settings. These can be changed through the Stream Dashboard.

Permission settings

Under the settings for your application, be sure to make sure Permission Checks are not disabled. Stream is built with a complex yet flexible permission system that checks if a user is able to perform certain actions based on their user role (think channel member vs moderator). Disabling this permission layer opens your application up to vulnerabilities, such as a user modifying another user's messages.

Channel Settings

Within each channel type (yes, these settings are configurable on a channel type basis), you are able to toggle on certain events. This translates to the different events that are transmitted through the WebSocket, and although increasing the feature set of a channel, also increase the load on the client.

Keep objects lightweight and use identifiers/enrich

It's always best practice to keep objects (users, channels, messages, reactions) lightweight in Stream. Particularly when it concerns custom data fields.

  • Enrich using URL's rather than storing data directly.
  • Avoid lengthy custom fields, particularly on channels that have larger member counts.
@tbarbugli
Copy link

we should have code examples on how to instrument Chat JS with Sentry and Rollbar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment