Skip to content

Connection to private servers on android #184

@vhiz

Description

@vhiz

For some reason, when you close your app and reopen it after some time, you cannot reconnect to the socket on Android unless you clear data on the app is this normal or it is from my code this is the init file

import useUserStore from '@/store/userStore';
import { Pusher, PusherEvent } from '@pusher/pusher-websocket-react-native';
import { broadcastClient } from './axiosClient';

const pusher = Pusher.getInstance();
let isPusherInitialized = false;
let isPusherConnected = false;
let connectPromise: Promise<void> | null = null;

export const initPusher = async () => {
  try {
    // Check if already initialized
    await pusher.reset();
    if (isPusherInitialized) {
      console.log('Pusher already initialized');
      return;
    }
    try {
      if (pusher?.connectionState === 'CONNECTED') {
        await pusher?.disconnect();
      }
    } catch (_) {}
    const { token } = useUserStore.getState();
    if (!token) {
      return;
    }
    await pusher.init({
      apiKey: process.env.EXPO_PUBLIC_PUSHER_API_KEY ?? '',
      cluster: process.env.EXPO_PUBLIC_CLUSTER ?? '',
      useTLS: true,
      authorizerTimeoutInSeconds: 120,
      onSubscriptionError(channelName, message, e) {
        console.log(`Subscription error on ${channelName}: ${message}`);
        console.log('Error details:', e);
      },
      onConnectionStateChange(currentState, previousState) {
        console.log('Pusher state:', previousState, '→', currentState);
        // SDK uses upper‑case connection states like "CONNECTED"
        const state = (currentState as string | undefined)?.toUpperCase?.();
        isPusherConnected = state === 'CONNECTED';
      },
      onError(message, code, e) {
        console.log('Pusher error:', message, code);
      },
      onDecryptionFailure(eventName, reason) {
        console.log('Decryption failure:', reason);
      },
      async onAuthorizer(channelName, socketId) {
        try {
          const res = await broadcastClient.post('', {
            channel_name: channelName,
            socket_id: socketId,
          });
          console.log('Authorization response:', res.data);
          return res.data;
        } catch (error) {
          console.error('Authorization error:', JSON.stringify(error));
          throw error; // Re-throw to let Pusher handle it
        }
      },
    });

    isPusherInitialized = true;

    // Ensure a single connect in flight and await it
    if (!connectPromise) {
      connectPromise = pusher
        .connect()
        .catch((err) => {
          console.error('Pusher connect() failed:', err);
          isPusherConnected = false;
          throw err;
        })
        .finally(() => {
          // allow future reconnect attempts
          connectPromise = null;
        });
    }

    await connectPromise;
    console.log('Pusher connected successfully');
  } catch (error) {
    console.error('Failed to initialize Pusher:', error);
    isPusherInitialized = false;
    isPusherConnected = false;
    throw error;
  }
};

export const subscribeToChannel = async (
  channelName: string,
  onEvent: (event: PusherEvent) => void
) => {
  try {
    // CRITICAL: Ensure Pusher is initialized AND connected
    if (!isPusherInitialized) {
      console.warn('Pusher not initialized, initializing now...');
      await initPusher();
    }

    // If a connect() is in progress, wait for it
    if (connectPromise) {
      console.log('Awaiting existing Pusher connectPromise before subscribing...');
      await connectPromise;
    }

    // Wait for connection if not connected yet (with timeout)
    if (!isPusherConnected) {
      console.log('Waiting for Pusher connection...');
      await new Promise((resolve) => {
        const checkInterval = setInterval(() => {
          if (isPusherConnected) {
            clearInterval(checkInterval);
            resolve(true);
          }
        }, 100);

        // Timeout after 10 seconds
        setTimeout(() => {
          console.warn('Pusher connection wait timed out after 10s');
          clearInterval(checkInterval);
          resolve(false);
        }, 10000);
      });
    }

    // Validate channel name
    if (!channelName || channelName.trim() === '') {
      throw new Error('Invalid channel name');
    }

    await pusher.subscribe({
      channelName: channelName.trim(),
      onEvent: (event) => {
        console.log('Pusher event:', event);
        onEvent(event);
      },
    });

    console.log('Subscribed to channel:', channelName);
  } catch (error) {
    console.error('Subscription error for', channelName, ':', error);
    throw error;
  }
};

export const triggerClientEvent = async (channelName: string, eventName: string, data: any) => {
  try {
    if (!isPusherInitialized) {
      await initPusher();
    }

    if (connectPromise) {
      console.log('Awaiting existing Pusher connectPromise before triggering event...');
      await connectPromise;
    }

    if (!isPusherConnected) {
      console.log('Waiting for Pusher connection...');
      await new Promise((resolve) => {
        const checkInterval = setInterval(() => {
          if (isPusherConnected) {
            clearInterval(checkInterval);
            resolve(true);
          }
        }, 100);

        // Timeout after 10 seconds
        setTimeout(() => {
          console.warn('Pusher connection wait timed out after 10s (triggerClientEvent)');
          clearInterval(checkInterval);
          resolve(false);
        }, 10000);
      });
    }
    if (!eventName.startsWith('client-')) {
      throw new Error('Client event names MUST start with "client-"');
    }

    await pusher.trigger({
      channelName,
      eventName,
      data: JSON.stringify(data),
    });

    console.log('Client event sent:', eventName, data);
  } catch (err) {
    console.error('Failed to trigger client event:', err);
  }
};
export const unSubscribeToChannel = async (channelName: string) => {
  try {
    if (!isPusherInitialized || !channelName) {
      console.log('Skipping unsubscribe - not initialized or invalid channel');
      return;
    }

    await pusher.unsubscribe({
      channelName,
    });
    console.log('Unsubscribed from channel:', channelName);
  } catch (error) {
    console.error('Unsubscribe error for', channelName, ':', error);
  }
};

export const disconnectPusher = async () => {
  try {
    if (isPusherConnected && isPusherInitialized) {
      await pusher.disconnect();
      connectPromise = null;
      isPusherConnected = false;
      console.log('Pusher disconnected successfully');
    } else {
      console.log('Pusher already disconnected');
    }
  } catch (error) {
    console.warn('Failed to disconnect pusher:', error);
    isPusherConnected = false;
  }
};

// Helper function to check connection status
export const isPusherReady = () => {
  return isPusherInitialized && isPusherConnected;
};

export default pusher;


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions