Clean up. Almost ready to deploy maybe. REally wanna rewrite but hey eventually we will.
This commit is contained in:
147
src/lib/hooks/useStatusSubscription.ts
Normal file
147
src/lib/hooks/useStatusSubscription.ts
Normal file
@ -0,0 +1,147 @@
|
||||
'use client';
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { createClient } from '@/utils/supabase';
|
||||
import type { RealtimeChannel } from '@supabase/supabase-js';
|
||||
|
||||
export type ConnectionStatus =
|
||||
| 'connecting'
|
||||
| 'connected'
|
||||
| 'disconnected'
|
||||
| 'updating';
|
||||
|
||||
type UseStatusSubscriptionOptions = {
|
||||
enabled?: boolean;
|
||||
onStatusUpdate?: () => void;
|
||||
maxReconnectAttempts?: number;
|
||||
reconnectDelay?: number;
|
||||
}
|
||||
|
||||
export const useStatusSubscription = ({
|
||||
enabled = true,
|
||||
onStatusUpdate,
|
||||
maxReconnectAttempts = 5,
|
||||
reconnectDelay = 2000,
|
||||
}: UseStatusSubscriptionOptions = {}) => {
|
||||
const [connectionStatus, setConnectionStatus] =
|
||||
useState<ConnectionStatus>('disconnected');
|
||||
const channelRef = useRef<RealtimeChannel | null>(null);
|
||||
const supabaseRef = useRef(createClient());
|
||||
const reconnectAttemptsRef = useRef(0);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const isComponentMountedRef = useRef(true);
|
||||
const visibilityTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
if (reconnectTimeoutRef.current) {
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
reconnectTimeoutRef.current = undefined;
|
||||
}
|
||||
if (visibilityTimeoutRef.current) {
|
||||
clearTimeout(visibilityTimeoutRef.current);
|
||||
visibilityTimeoutRef.current = undefined;
|
||||
}
|
||||
if (channelRef.current) {
|
||||
supabaseRef.current.removeChannel(channelRef.current).catch((error) => {
|
||||
console.error('❌ cleanup: Error removing channel:', error);
|
||||
});
|
||||
channelRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (!enabled || !isComponentMountedRef.current) return;
|
||||
|
||||
cleanup();
|
||||
setConnectionStatus('connecting');
|
||||
|
||||
const channel = supabaseRef.current
|
||||
.channel('status_updates', {
|
||||
config: { broadcast: {self: true }}
|
||||
});
|
||||
channel
|
||||
.on('broadcast', { event: 'status_updated' }, (payload) => {
|
||||
onStatusUpdate?.();
|
||||
})
|
||||
.subscribe((status) => {
|
||||
if (!isComponentMountedRef.current) return;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
||||
if (status === 'SUBSCRIBED') {
|
||||
setConnectionStatus('connected');
|
||||
reconnectAttemptsRef.current = 0;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
||||
} else if (status === 'CHANNEL_ERROR' || status === 'CLOSED') {
|
||||
setConnectionStatus('disconnected');
|
||||
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
|
||||
reconnectAttemptsRef.current++;
|
||||
const delay = reconnectDelay * reconnectAttemptsRef.current;
|
||||
|
||||
reconnectTimeoutRef.current = setTimeout(() => {
|
||||
if (isComponentMountedRef.current) connect();
|
||||
}, delay);
|
||||
} else {
|
||||
console.warn('⚠️ connect: Max reconnection attempts reached');
|
||||
setConnectionStatus('disconnected');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
channelRef.current = channel;
|
||||
}, [enabled, onStatusUpdate, maxReconnectAttempts, reconnectDelay, cleanup]);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
cleanup();
|
||||
setConnectionStatus('disconnected');
|
||||
}, [cleanup]);
|
||||
|
||||
const reconnect = useCallback(() => {
|
||||
reconnectAttemptsRef.current = 0;
|
||||
connect();
|
||||
}, [connect]);
|
||||
|
||||
// Handle visibility change for better reconnection
|
||||
useEffect(() => {
|
||||
const handleVisibilityChange = () => {
|
||||
if (!enabled) return;
|
||||
if (document.visibilityState === 'visible') {
|
||||
visibilityTimeoutRef.current = setTimeout(() => {
|
||||
if (connectionStatus === 'disconnected' && isComponentMountedRef.current) {
|
||||
reconnect();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
};
|
||||
}, [enabled, connectionStatus, reconnect]);
|
||||
|
||||
// Initial connection - SIMPLIFIED to avoid dependency issues
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
const initialTimeout = setTimeout(() => {
|
||||
if (isComponentMountedRef.current) connect();
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(initialTimeout);
|
||||
};
|
||||
}, [enabled]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanup();
|
||||
};
|
||||
}, [cleanup]);
|
||||
|
||||
return {
|
||||
connectionStatus,
|
||||
connect: reconnect,
|
||||
disconnect,
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user