No user event for being invited or joining a channel?

Curious how we are supposed determine when the user has been invited to a channel they are not subscribing to events for yet (because the client hasn’t known to attach the handlers channel events). I would imagine we would need an event handler on the user, but we only have onFriendsDiscovered and onTotalUnreadMessageCountUpdated. Don’t see it documented anywhere either.

Not using the syncmanager due to implementation issues, just regular sendbird-sdk for js.

Simple use-case is creating a channel in the sendbird dashboard with some users in it. They only way the devices will know they are in a channel is the next time they do a list query for channels.

Appreciate the help!

you should register channel event handler at the beginning of your application flow

1 Like

Woo is correct that you should be registering the channelHandler at the beginning of your application flow. Then you’d utilize the onUserReceivedInvitation() handler to process incoming invitation requests.

@Tyler @Woo Hmm… I thought channel event handlers need to be done for every channel. In this case the application doesn’t yet know about this new channel. Am I wrong in that you need to subscribe to each channel individually, or does one channel handler work for any number of channels?

Thanks!

@bneigher The handlers work for any number of channels. In my personal application, I register all handlers immediately after I authenticate and have not had any issues.

@Tyler sorry to be super specific here, but when you say “register all handlers immediately” do you mean one connection handler, one channel handler, and optionally one user handler? This is a great help btw I just removed the loop over channels and it appears to be working idk why I thought we needed to subscribe to each channel’s events independently.

Correct, I register one channel, connection and user handler with all of the events I want to subscribe to.

Amazing. I did this all in a HOC… sharing incase anyone is curious

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

// saved on the outer scope because this is used in react-native-navigation, the HOC has a new instance for every scene, this caches it
const Register = {
  AppState: null,
  Connection: null,
  ChannelEventHandler: null,
};

// prettier-ignore
// throttled because this is used in react-native-navigation, the HOC has a new instance for every scene
const throttledEventInitializer = _throttle(
  async (props, SB) => {
    if (!Register.Connection) {
      console.log('[Sendbird] Registering Connection Handler');
      Register.Connection = new SB.ConnectionHandler();
      Register.Connection.onReconnectFailed = () => {
        console.log("onReconnectFailed");
        SB.reconnect();
      };
      Register.Connection.onReconnectStarted = () => {
        console.log('onReconnectStarted');
      };
      Register.Connection.onReconnectSucceeded = () => {
        console.log('onReconnectSucceeded');
        props.fetchChannels()
      };
      SB.addConnectionHandler('connection', Register.Connection);
    }
    if (!Register.ChannelEventHandler) {
      console.log('[Sendbird] Registering Channel Handler');
      Register.ChannelEventHandler = new SB.ChannelHandler();
      Register.ChannelEventHandler.onMessageReceived = (channel, message) => { props.messageEventHandler('insert', channel, [message]); };
      Register.ChannelEventHandler.onMessageUpdated = (channel, message) => { props.messageEventHandler('update', channel, [message]); };
      Register.ChannelEventHandler.onMessageDeleted = (channel, messageId) => { props.messageEventHandler('remove', channel, [{ messageId }]); };
      Register.ChannelEventHandler.onMentionReceived = (channel,message) => {};
      Register.ChannelEventHandler.onDeliveryReceiptUpdated = groupChannel => {};
      Register.ChannelEventHandler.onReadReceiptUpdated = groupChannel => { console.log('someone read a message'); };
      Register.ChannelEventHandler.onTypingStatusUpdated = groupChannel => { console.log('someone is typing'); };
      Register.ChannelEventHandler.onChannelHidden = groupChannel => { props.channelEventHandler('remove', [groupChannel]); };
      Register.ChannelEventHandler.onChannelChanged = channel => { props.channelEventHandler('update', [channel]); };
      Register.ChannelEventHandler.onChannelDeleted = (channelUrl, channelType) => { props.channelEventHandler('remove', [{ url: channelUrl }]); };
      Register.ChannelEventHandler.onChannelFrozen = channel => { props.channelEventHandler('remove', [channel]); };
      Register.ChannelEventHandler.onChannelUnfrozen = channel => { props.channelEventHandler('insert', [channel]); };
      Register.ChannelEventHandler.onChannelMemberCountChanged = channels => {};
      Register.ChannelEventHandler.onChannelParticipantCountChanged = channels => {};
      Register.ChannelEventHandler.onMetaDataCreated = (channel, metaData) => {};
      Register.ChannelEventHandler.onMetaDataUpdated = (channel, metaData) => {};
      Register.ChannelEventHandler.onMetaDataDeleted = (channel, metaDataKeys) => {};
      Register.ChannelEventHandler.onMetaCountersCreated = ( channel, metaCounter) => {};
      Register.ChannelEventHandler.onMetaCountersUpdated = (channel, metaCounter) => {};
      Register.ChannelEventHandler.onMetaCountersDeleted = (channel, metaCounterKeys) => {};
      Register.ChannelEventHandler.onUserEntered = (openChannel, user) => { console.log('user entered'); };
      Register.ChannelEventHandler.onUserExited = (openChannel, user) => { console.log('user exited'); };
      Register.ChannelEventHandler.onUserMuted = (channel, user) => { props.channelEventHandler('remove', [channel]); };
      Register.ChannelEventHandler.onUserUnmuted = (channel, user) => { props.channelEventHandler('insert', [channel]); };
      Register.ChannelEventHandler.onUserBanned = (channel, user) => { props.channelEventHandler('remove', [channel]); };
      Register.ChannelEventHandler.onUserUnbanned = (channel, user) => { props.channelEventHandler('insert', [channel]); };
      Register.ChannelEventHandler.onUserReceivedInvitation = (groupChannel, inviter, invitees) => { props.channelEventHandler('insert', [groupChannel]) };
      Register.ChannelEventHandler.onUserDeclinedInvitation = (groupChannel, inviter, invitee) => {};
      Register.ChannelEventHandler.onUserJoined = (groupChannel, user) => { console.log('user joined', groupChannel); };
      Register.ChannelEventHandler.onUserLeft = (groupChannel, user) => { console.log('user left', groupChannel); };
      SB.addChannelHandler('channels', Register.ChannelEventHandler);
    }
    if (!Register.AppState) {
      Register.AppState = AppState.addEventListener(
        "change",
        nextAppState => {
          const SB = SendBird.getInstance();
          if (
            nextAppState === "active"
          ) {
            SB.setForegroundState();
          } else {
            SB.setBackgroundState();
          }
        }
      );
    }
  },
  10000,
  {
    leading: true,
    trailing: false,
  },
);

// throttled because this is used in react-native-navigation, the HOC has a new instance for every scene
const throttledEventCleaner = _throttle(
  async SB => {
    console.log('[Sendbird] Unregistering (Cleaner)');
    Register.AppState = null;
    Register.Connection = null;
    SB.removeChannelHandler('channels');
    Register.ChannelEventHandler = null;
  },
  3000,
  {
    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 SB = SendBird.getInstance();

  useEffect(() => {
    if (props.chat.user.userId && props.auth.user_id) {
      throttledEventInitializer(props, SB); // SET event handlers
    } else if (!props.auth.user_id) {
      throttledEventCleaner(SB); // logged out, DELETE event handlers
    }
  }, [props.chat.session_token.expires_at]); // creation or update of token expiration

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

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

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

export { SendbirdHOC, useSendbird, SendbirdContext };

These handlers correspond to the actions described in the react-native-redux-syncmanager-sample, and should plug into the reducers as is.