UNPKG

live-react-native-elixir-test

Version:

React Native adapter for Phoenix LiveView reactivity

156 lines (155 loc) 5.16 kB
// TODO: Implement in Phase 1.3 import { Socket } from 'phoenix'; export class LiveViewChannel { constructor(options) { this.channel = null; this.currentTopic = null; this.connectionCallbacks = []; this.errorCallbacks = []; this.maxReconnectAttemptsCallback = null; this.maxReconnectAttempts = options.maxReconnectAttempts || 5; this.connectionState = { connected: false, connecting: false, error: null, reconnectAttempt: 0, }; const socketOptions = {}; if (options.params) { socketOptions.params = options.params; } if (options.reconnectDelay) { socketOptions.reconnectAfterMs = options.reconnectDelay; } else { // Default exponential backoff: [1000, 2000, 5000, 10000, 30000] socketOptions.reconnectAfterMs = (tries) => { const delays = [1000, 2000, 5000, 10000, 30000]; return delays[tries - 1] || 30000; // cap at 30s }; } this.socket = new Socket(options.url, socketOptions); } connect() { this.connectionState.connecting = true; this.socket.onOpen(() => { this.connectionState.connected = true; this.connectionState.connecting = false; this.connectionState.error = null; this.connectionState.reconnectAttempt = 0; // Reset on successful connection this.connectionCallbacks.forEach(callback => callback(true)); }); this.socket.onClose(() => { this.connectionState.connected = false; this.connectionState.connecting = false; this.connectionCallbacks.forEach(callback => callback(false)); }); this.socket.onError((error) => { this.connectionState.error = error; this.connectionState.connecting = false; this.connectionState.reconnectAttempt += 1; this.errorCallbacks.forEach(callback => callback(error)); // Check if we've exceeded max reconnect attempts if (this.connectionState.reconnectAttempt >= this.maxReconnectAttempts) { if (this.maxReconnectAttemptsCallback) { this.maxReconnectAttemptsCallback(); } } }); this.socket.connect(); } disconnect() { if (this.channel) { this.channel.leave(); this.channel = null; this.currentTopic = null; } this.socket.disconnect(); this.connectionState.connected = false; this.connectionState.connecting = false; } joinLiveView(topic, params = {}, options = {}) { this.channel = this.socket.channel(topic, params); this.currentTopic = topic; this.channel.join() .receive('ok', (response) => { if (options.onJoin) { options.onJoin(response); } }) .receive('error', (error) => { if (options.onError) { options.onError(error); } }) .receive('timeout', () => { if (options.onError) { options.onError({ reason: 'timeout' }); } }); this.channel.onClose(() => { this.currentTopic = null; }); this.channel.onError((error) => { this.errorCallbacks.forEach(callback => callback(error)); }); } leaveLiveView(options = {}) { if (!this.channel) { return; } this.channel.leave() .receive('ok', () => { if (options.onLeave) { options.onLeave(); } }); this.channel = null; this.currentTopic = null; } pushEvent(event, payload = {}, options = {}) { if (!this.channel) { throw new Error('Cannot push event: no LiveView channel joined'); } this.channel.push(event, payload) .receive('ok', (response) => { if (options.onSuccess) { options.onSuccess(response); } }) .receive('error', (error) => { if (options.onError) { options.onError(error); } }); } onAssignsUpdate(callback) { if (!this.channel) { return; } this.channel.on('assigns_update', callback); } // Event handler registration onConnectionChange(callback) { this.connectionCallbacks.push(callback); } onError(callback) { this.errorCallbacks.push(callback); } onMaxReconnectAttempts(callback) { this.maxReconnectAttemptsCallback = callback; } // State getters isConnected() { return this.connectionState.connected; } getCurrentTopic() { return this.currentTopic; } getReconnectAttempts() { return this.connectionState.reconnectAttempt; } getConnectionState() { return { ...this.connectionState }; } }