Sendbird-syncmanager stops firing events (cache) for some reason after normal (online) usage

I’m implementing sendbird into my react-native app and have been scratching my head why the syncmanager keeps caching events instead of actually calling the event handler. It happens after a few operations - basically making the ability for people to leave and join channels unusable in real time as the operation correctly seems to be handled in the syncmanager (from the logs) but I can see that the actual event handler is not being called leaving me to manually call the specific handler so that my redux state is accurate. This is a problem as whenever I return the to app, the sync manager will decide to flush the cached events and essentially fire the same chain events again. This breaks my state.

This could be a developer/operator issue though - but would appreciate some help as the docs are lacking, and the example project (react-native-redux-syncmanager-sample) doesn’t work.

Suppose I have added a channel (how I do that is in the second code block), and then I device to hide that channel.

I see a normal event chain, and my channel handler responding to this:

channel.hide(true, true)

and then when I add the channel back:

const sb = SendBird.getInstance();
const params = new sb.GroupChannelParams();
params.isPublic = false;
params.isEphemeral = false;
params.isDistinct = true;
params.isSuper = false;
params.addUserIds(['99999999', auth.user_id]); // hard coded support team user and the client user
params.name = 'Support';
await sb.GroupChannel.createChannel(params);

Only after I refresh the app does the cached event execute the handler.

I have a HOC which manages the connection to sendbird and attaches the event listeners. It responds to the results of me authenticating with sendbird.connect, where the user object is inserted in the props.chat.user redux store.

import React, { createContext, useEffect, useContext, useState } from 'react';
import useAppState from 'react-native-appstate-hook';
import { connect } from 'react-redux';
import * as actions from 'actions';
import SendBird from 'sendbird';
import SendBirdSyncManager from 'sendbird-syncmanager';
import _throttle from 'lodash.throttle';

const syncOptions = (SB, user) => {
  const options = new SendBirdSyncManager.Options();
  options.messageCollectionCapacity = 2000;
  options.messageResendPolicy = 'automatic';
  options.failedMessageRetentionDays = 7;
  options.maxFailedMessageCountPerChannel = 50;
  options.automaticMessageResendRetryCount = 4;
  return options;
};

const queryOptions = (SB, user) => {
  const query = SB.GroupChannel.createMyGroupChannelListQuery();
  query.limit = 50;
  query.includeEmpty = true;
  query.hiddenChannelFilter = SB.GroupChannel.HiddenChannelFilter.UNHIDDEN;
  query.order = 'latest_last_message';
  return new SendBirdSyncManager.ChannelCollection(query);
};

const Register = {
  Connection: null,
  Collection: null,
};

const throttledChatEventInitializer = _throttle(
  async (props, SB, setCollection) => {
    if (__DEV__) {
      // SendBird.setLogLevel(SendBird.LogLevel.VERBOSE);
      SendBirdSyncManager.loggerLevel = 98765;
    }

    if (!Register.Connection || !Register.Collection) {
      await SendBirdSyncManager.setup(
        props.chat.user.userId,
        syncOptions(SB, props.chat.user),
      );
    }
    if (!Register.Connection) {
      // Sync Connection
      Register.Connection = new SB.ConnectionHandler();
      Register.Connection.onReconnectFailed = () => {
        SB.reconnect();
      };
      Register.Connection.onReconnectStarted = () => {
        console.log('Connection Started');
        const manager = SendBirdSyncManager.getInstance();
        manager.pauseSync();
      };
      Register.Connection.onReconnectSucceeded = () => {
        console.log('onReconnectSucceeded');
        const manager = SendBirdSyncManager.getInstance();
        manager.resumeSync();
      };
      console.log('adding connection handler');
      SB.addConnectionHandler('connection', Register.Connection);
    }
    if (!Register.Collection) {
      // Sync Channels
      Register.Collection = queryOptions(SB, props.chat.user);
      const channelHandler =
        new SendBirdSyncManager.ChannelCollection.CollectionHandler();
      channelHandler.onChannelEvent = (action, channels) => {
        props.channelEventHandler(action, channels);
      };
      Register.Collection.setCollectionHandler(channelHandler);
      Register.Collection.fetch();
      setCollection(Register.Collection);
    }
  },
  10000,
  {
    leading: true,
    trailing: false,
  },
);

const SendbirdContext = createContext();

function useSendbird() {
  const context = useContext(SendbirdContext);
  if (context === undefined) {
    throw new Error('useSendbird must be used within a SendbirdProvider');
  }
  return context;
}

function SendbirdProvider(props) {
  const [collection, setCollection] = useState(Register && Register.Collection);
  const SB = SendBird.getInstance();
  const { appState } = useAppState({
    onChange: newAppState => {
      if (newAppState !== appState) {
        if (newAppState === 'active') {
          SB.setForegroundState();
        } else {
          SB.setBackgroundState();
        }
      }
    },
    onForeground: () => {
      // if (Register.Collection) {
      //   Register.Collection.fetch();
      // }
    },
    onBackground: () => {},
  });

  useEffect(() => {
    if (props.chat.user.userId) {
      throttledChatEventInitializer(props, SB, setCollection);
    } else {
      if (Register && Register.Connection) {
        try {
          SB.removeConnectionHandler('connection');
        } catch (err) {}
      }
      SB.removeChannelHandler('channels');
    }
  }, [props.chat.user.userId]);

  return (
    <SendbirdContext.Provider
      value={{
        collection: collection,
      }}>
      {props.children}
    </SendbirdContext.Provider>
  );
}

const mapStateToProps = ({ auth, chat }) => ({
  auth,
  chat,
});

const SendbirdHOC = connect(mapStateToProps, actions)(SendbirdProvider);

export { SendbirdHOC, useSendbird, SendbirdContext };

Hey @bneigher,

Sorry for the delayed response on this. I think it may be worth waiting to implement Syncmanager any further. We’re releasing local caching built into the core SDKs soon. As a result we’ll be sunsetting the SyncManager SDKs. It may not be worth spinning your head over these problems when that is just around the corner.

Is local caching an immediate requirement for your deployment?

@Tyler negative. I’ve already went ahead and ejected syncmanager and just went with the standard sdk. Perhaps putting a deprecated message in the github is a good move to prevent others from getting themself into technical debt.

Is there going to be a complete implantation change from the v3 js sdk to the one with caching baked in? Or will it be a relatively simple upgrade?

Thanks!