pusher / pusher-websocket-react-native

React Native official Pusher SDK
MIT License
62 stars 55 forks source link

Why does pusher-websocket-react-native sometimes work and sometimes just doesn't? #111

Closed AntonioA-D-C closed 8 months ago

AntonioA-D-C commented 1 year ago

onConnectionStateChange. previousState=DISCONNECTED newState=CONNECTING onConnectionStateChange. previousState=DISCONNECTED newState=CONNECTING onConnectionStateChange. previousState=DISCONNECTING newState=DISCONNECTED onConnectionStateChange. previousState=DISCONNECTING newState=DISCONNECTED onConnectionStateChange. previousState=CONNECTING newState=CONNECTED onConnectionStateChange. previousState=CONNECTING newState=CONNECTED

This is the connection data on my console

And this is the data that tells me the subscription was successful

onConnectionStateChange. previousState=DISCONNECTED newState=CONNECTING
onConnectionStateChange. previousState=CONNECTING newState=CONNECTED
This is a success I guess
onSubscriptionSucceeded: userChannel-3
PusherChannel {
  "channelName": "userChannel-3",
  "me": undefined,
  "members": Map {},
  "onEvent": [Function onEvent],
  "onMemberAdded": undefined,
  "onMemberRemoved": undefined,
  "onSubscriptionCount": undefined,
 "onSubscriptionSucceeded": undefined,
}

When I try sending data be it through postMan or the App, it refuses to do a single thing, doesn't give me any errors either, it only works sometimes.

This might seem vague, but I haven't genuinely gotten any signal of why this is happenning, I'm logging every possible action that could be responsible, it just seems like the onEvent method is never triggered at all, yet sometimes it works perfectly.

  const pusherCtx = useContext(PusherContext);

  useEffect(()=>{
    async function getPusher(){

   pusherCtx.startPusher(`userChannel-${userID}`, 'ThisEvent');

    }
   getPusher();
  }, [data])

The context

import {useState, useContext, useEffect, createContext} from 'react';
import { Pusher, PusherEvent } from '@pusher/pusher-websocket-react-native';
import { APP_CLUSTER, APP_ENDPOINT, APP_KEY } from '../util/pusher_credentials';
import { QueryClient, useQueryClient } from '@tanstack/react-query';

export const PusherContext = createContext({
    pusher: '',
    apiKey: APP_KEY,
    cluster: APP_CLUSTER,
    authEndpoint: APP_ENDPOINT,
    port: 6001, 
    refetchThis: '',
    thisData:{},
    onConnectionStateChange: (currentState, previousState)=>{},
    onError: (message, code, error)=>{},
    onEvent: (event)=>{},
    onSubscriptionSucceeded: (channelName)=>{},
    onSubscriptionError: (channelName, message, e)=>{},
    onDecryptionFailure: (eventName, reason)=>{},
    subscribeToChannel: (channelName)=>{}
});

function PusherProvider({children}){
    const queryClient= useQueryClient();
    const [pusher, setPusher] = useState(Pusher.getInstance());
    const [thisData, setData] = useState({});
    const [apiKey, setApiKey] = useState(APP_KEY);
    const [cluster, setCluster]= useState(APP_CLUSTER);
    const [authEndpoint, setAuthEndpoint]= useState(APP_ENDPOINT)
    const [port, setPort]= useState(6001)
    const [refetchThis, setRefetchThis] = useState('');

    function onEvent(event: PusherEvent){
        console.log("Technically, I should be called, but that's not working")
        console.warn(`onEvent> ${event}`)

      setData({...event})
      console.log(thisData)

      }

    function onSubscriptionSucceeded(channelName){
        console.log("This is a success I guess")

        console.log(
          `onSubscriptionSucceeded: ${channelName}`);

        const channel= pusher.getChannel(channelName);

        if (!channel) {
         console.log("Error");
         return
        }
        const me = channel;
      //  onChangeMembers([...channel.members.values()]);
        console.log(me);
      }

     function onError(message, code, error){
        console.log(`onError: ${message} code: ${code} exception: ${error}`);
      };

      function onSubscriptionError(channelName, message, e){
        console.log("Yikes this ian Error")
        console.log(`onSubscriptionError: ${message}, channelName: ${channelName} e: ${e}`);
      };

      function onDecryptionFailure(eventName, reason){
        console.log(`onDecryptionFailure: ${eventName} reason: ${reason}`);
      };

      function onConnectionStateChange (currentState, previousState) {
        console.log(
          `onConnectionStateChange. previousState=${previousState} newState=${currentState}`
        );
      };

      async function startPusher(channelName, refetch){
        setRefetchThis(refetch);
        console.log(refetchThis)
        console.warn(refetchThis)
        await pusher.init({
            apiKey:APP_KEY,
            cluster: APP_CLUSTER,
            authEndpoint: "http://127.0.0.1:8000/pusher/broadcasting/auth",
            port: 6001,
            onConnectionStateChange,
            onError,
            onEvent,
            onSubscriptionSucceeded,
            onSubscriptionError,
            onDecryptionFailure,

        }).catch(error=>console.error(error));
        await pusher.subscribe({channelName:channelName, onEvent });
        await pusher.connect();
      }
      async function subscribeToChannel(channelName, refetchThis){
        await pusher.subscribe({channelName:channelName, onEvent });
        setRefetchThis(refetchThis);
         }
      const value ={
        pusher:pusher,
        apiKey:apiKey,
        cluster: cluster,
        authEndpoint: authEndpoint,
        port:port,
        refetchThis: refetchThis,
        thisData: thisData,
        onConnectionStateChange: onConnectionStateChange,
        onError: onError,
        onEvent: onEvent,
        onSubscriptionSucceeded: onSubscriptionSucceeded,
        onSubscriptionError: onSubscriptionError,
        onDecryptionFailure:  onDecryptionFailure,
        subscribeToChannel: subscribeToChannel,
        startPusher: startPusher
      }
      return <PusherContext.Provider value={value}>{children}</PusherContext.Provider>
}

export default PusherProvider
xutopia commented 1 year ago

We've also experienced this issue where Pusher stops working and doesn't seem to produce any errors. A slight difference from @AntonioA-D-C's experience is that Pusher attempts to reconnect (ie: onConnectionStateChange. previousState=RECONNECTING newState=CONNECTING) but never successfully reaches the CONNECTED state.

 LOG  onSubscriptionSucceeded: name:  private-xxxx-xxx
 LOG  onSubscriptionSucceeded: data:  {}
 LOG  onSubscriptionSucceeded: name:  private-xxxx-xxx
 LOG  onSubscriptionSucceeded: data:  {}
 LOG  onConnectionStateChange. previousState=CONNECTED newState=DISCONNECTED
 LOG  onConnectionStateChange. previousState=DISCONNECTED newState=RECONNECTING
 LOG  onConnectionStateChange. previousState=RECONNECTING newState=CONNECTING

Not sure if it matters, but the above happened on the iOS simulator that was running since the night before. I only stumbled upon it this morning. Is there an event callback such as onConnectionStateFailure? So that we can manually trigger a reconnection with Pusher? Because so far, it seems that the trail ends with newState=CONNECTING and no error produced. Any help or insight would be greatly appreciated!

Specs:

benjamin-tang-pusher commented 1 year ago

@xutopia there's a known issue in our underlying Swift websocket library waiting rarely waiting indefinitely for the device to reconnect (but the iOS device has disposed of the connection. So it will wait forever, hence hanging on reconnecting). There's an experimental fork that should mitigate this.

@AntonioA-D-C Your case is more complex, and I suspect its because you are using a Context. I've used contexts in a provider before in React, but not in React Native. So although your app is establishing the connection and subscribing to the channel, the RN context probably isn't behaving as expecting when your client receives an event. Could you restructure your Pusher implementation away from using Contexts, and just initialize it in auseEffect(() => {...}, []); like our example?

phillipkey commented 1 year ago

@benjamin-tang-pusher are there any plans to merge the changes from the experimental fork? we would like to migrate to the new library but with pusher being a critical step in our business operations, we cannot afford to risk the flaky connections.

tdammy92 commented 1 year ago

@AntonioA-D-C Using a context with pusher will give you a very odd behaviour, you can refactor your code into a hook then consume where ever needed if possible.

AntonioA-D-C commented 12 months ago

So I can't use Pusher Data globally?

AntonioA-D-C commented 11 months ago

@xutopia there's a known issue in our underlying Swift websocket library waiting rarely waiting indefinitely for the device to reconnect (but the iOS device has disposed of the connection. So it will wait forever, hence hanging on reconnecting). There's an experimental fork that should mitigate this.

@AntonioA-D-C Your case is more complex, and I suspect its because you are using a Context. I've used contexts in a provider before in React, but not in React Native. So although your app is establishing the connection and subscribing to the channel, the RN context probably isn't behaving as expecting when your client receives an event. Could you restructure your Pusher implementation away from using Contexts, and just initialize it in auseEffect(() => {...}, []); like our example?

Alright, I stopped using context, and decided to initialize it in a useEffect like the example you mentioned

Now, Initially it works with zero issues, but when I travel to another screen, and come back it doesn't work anymore, why?

darshan09200 commented 11 months ago

I also encountered a similar issue and after debugging I found out that


After hot reload pusher.channels is still populated even though those channels are not connected

Not sure if this is the preferred approach but this is how I fixed it

const pusher = Pusher.getInstance()
const init = async () => {
  // Rest of the code
  if (pusher.channels.size > 0) {
    const promises = [];

    pusher.channels.forEach(({channelName}) =>
      promises.push(pusher.unsubscribe({channelName})),
    );

    await Promise.allSettled(promises);
  }
  pusher.init({ ... })
  // Rest of the code
}

The reason behind a large number of logs for CONNECTED, DISCONNECTED and all the other states is that ON_CONNECTION_STATE_CHANGE listener is not being removed in removeAllListeners

https://github.com/pusher/pusher-websocket-react-native/blob/067f8ff1133c780aedb257659f62d569eaf0c2b7/src/index.tsx#L307-L315

Just add the following code in removeAllListeners and the events will be logged only for one connection

    this.pusherEventEmitter.removeAllListeners(
      PusherEventName.ON_CONNECTION_STATE_CHANGE
    );

If you want to use a patch file follow the steps below

Steps to use the patch file

  1. Create a folder named patches in your root directory
  2. Create a file named @pusher+pusher-websocket-react-native+1.3.0.patch

    1.3.0 is the version (latest) I am using make sure to check you are using in your package.json

  3. Past the following code in the file

    
    diff --git a/node_modules/@pusher/pusher-websocket-react-native/src/index.tsx b/node_modules/@pusher/pusher-websocket-react-native/src/index.tsx
    index 5c04965..6bff3b7 100644
    --- a/node_modules/@pusher/pusher-websocket-react-native/src/index.tsx
    +++ b/node_modules/@pusher/pusher-websocket-react-native/src/index.tsx
    @@ -312,6 +312,9 @@ export class Pusher {
     this.pusherEventEmitter.removeAllListeners(
       PusherEventName.ON_MEMBER_REMOVED
     );
    +    this.pusherEventEmitter.removeAllListeners(
    +      PusherEventName.ON_CONNECTION_STATE_CHANGE
    +    );
    }
    
    public async reset() {

4. Run  the following command

npx patch-package



You can read more about `patch-package` and how to configure it in your project [here](https://github.com/ds300/patch-package?tab=readme-ov-file#readme).
benw-pusher commented 8 months ago

We recently updated the library to use a newer Swift dependency that resolves the issue mentioned in https://github.com/pusher/pusher-websocket-react-native/issues/111#issuecomment-1773252321

@AntonioA-D-C I can see you raised https://github.com/pusher/pusher-websocket-react-native/issues/123 as a follow up so I will close this thread

AntonioA-D-C commented 8 months ago

I solved the issue

The problem was I was calling Pusher with each re-render which caused pusher to act in erratic ways

This was fixed

Sorry for the inconvenience