diff --git a/__tests__/lib/weechat/connection.ts b/__tests__/lib/weechat/connection.ts index c21476d..1cdf1c2 100644 --- a/__tests__/lib/weechat/connection.ts +++ b/__tests__/lib/weechat/connection.ts @@ -93,7 +93,7 @@ describe(WeechatConnection, () => { expect(dispatch).toHaveBeenCalledWith(fetchVersionAction('4.1.2')); }); - describe('close', () => { + describe('disconnect', () => { it('closes the WebSocket', () => { const dispatch = jest.fn(); const connection = new WeechatConnection( @@ -115,12 +115,10 @@ describe(WeechatConnection, () => { ) } as WebSocketMessageEvent); - connection.close(); + connection.disconnect(); - expect(mockWebSocket.mock.instances[0].send).toHaveBeenCalledWith( - 'quit\n' - ); expect(mockWebSocket.mock.instances[0].close).toHaveBeenCalled(); + expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenNthCalledWith(2, disconnectAction()); expect(mockWebSocket.mock.instances).toHaveLength(1); }); @@ -150,6 +148,7 @@ describe(WeechatConnection, () => { mockWebSocket.mock.instances[0].close(); expect(onError).toHaveBeenCalledWith(true, ConnectionError.Socket); + expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenNthCalledWith(2, disconnectAction()); expect(mockWebSocket.mock.instances).toHaveLength(2); }); diff --git a/src/lib/weechat/connection.ts b/src/lib/weechat/connection.ts index 7820acb..688ed32 100644 --- a/src/lib/weechat/connection.ts +++ b/src/lib/weechat/connection.ts @@ -10,6 +10,13 @@ export enum ConnectionError { Authentication } +enum State { + CONNECTING = 1, + AUTHENTICATING, + CONNECTED, + DISCONNECTED +} + export default class WeechatConnection { dispatch: AppDispatch; hostname: string; @@ -22,9 +29,8 @@ export default class WeechatConnection { reconnect: boolean, connectionError: ConnectionError | null ) => void; - connected: boolean; - reconnect: boolean; - authenticating: boolean; + reconnect = false; + state = State.DISCONNECTED; constructor( dispatch: AppDispatch, @@ -43,10 +49,11 @@ export default class WeechatConnection { this.ssl = ssl; this.onSuccess = onSuccess; this.onError = onError; - this.reconnect = this.connected = this.authenticating = false; } connect(): void { + if (this.state !== State.DISCONNECTED) return; + this.state = State.CONNECTING; this.openSocket(); } @@ -62,7 +69,7 @@ export default class WeechatConnection { } onopen(): void { - this.authenticating = true; + this.state = State.AUTHENTICATING; this.send( `init password=${this.password},compression=${ @@ -74,36 +81,41 @@ export default class WeechatConnection { handleError(event: Event): void { console.log(event); - this.reconnect = this.connected; + this.reconnect = this.state === State.CONNECTED; this.onError(this.reconnect, ConnectionError.Socket); } handleClose(): void { - if (this.authenticating) { + if (this.state === State.AUTHENTICATING) { + this.state = State.DISCONNECTED; this.onError(false, ConnectionError.Authentication); return; } - this.connected = false; + if (this.state === State.DISCONNECTED) return; + + this.state = State.DISCONNECTED; this.dispatch(disconnectAction()); if (this.reconnect) { this.reconnect = false; - this.openSocket(); + this.connect(); } } - close(): void { - this.send('quit'); - this.websocket?.close(); + disconnect(): void { + this.state = State.DISCONNECTED; + if (this.websocket?.readyState === WebSocket.OPEN) { + this.websocket.close(); + } + this.dispatch(disconnectAction()); } onmessage(event: WebSocketMessageEvent): void { const parsed = protocol.parse(event.data); if (parsed.id === 'version') { - this.authenticating = false; - this.connected = true; + this.state = State.CONNECTED; this.onSuccess(this); } @@ -123,4 +135,8 @@ export default class WeechatConnection { console.log('Sending data:', data); this.websocket.send(data + '\n'); } + + isDisconnected(): boolean { + return this.state === State.DISCONNECTED; + } } diff --git a/src/usecase/ConnectionGate.tsx b/src/usecase/ConnectionGate.tsx index 252a6f1..4a73c67 100644 --- a/src/usecase/ConnectionGate.tsx +++ b/src/usecase/ConnectionGate.tsx @@ -1,6 +1,7 @@ -import { connect } from 'react-redux'; -import { StoreState } from '../store'; +import { useEffect } from 'react'; +import { connect, useStore } from 'react-redux'; import { ConnectionError } from '../lib/weechat/connection'; +import { StoreState } from '../store'; import SettingsNavigator from './settings/SettingsNavigator'; interface Props { @@ -18,6 +19,19 @@ const ConnectionGate: React.FC = ({ onConnect, connectionError }) => { + const store = useStore(); + + useEffect(() => { + const connectionSettings = store.getState().connection; + if (connectionSettings.hostname && connectionSettings.password) { + onConnect( + connectionSettings.hostname, + connectionSettings.password, + connectionSettings.ssl + ); + } + }, [onConnect, store]); + if (connected) { return children; } else { diff --git a/src/usecase/Root.tsx b/src/usecase/Root.tsx index e14eb07..6f20fa3 100644 --- a/src/usecase/Root.tsx +++ b/src/usecase/Root.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { StatusBar } from 'react-native'; +import { AppState, StatusBar } from 'react-native'; import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; @@ -9,10 +9,10 @@ import { persistor, store } from '../store'; import { addListener } from '@reduxjs/toolkit'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { getPushNotificationStatusAsync } from '../lib/helpers/push-notifications'; +import { upgradeAction } from '../store/actions'; import App from './App'; import ConnectionGate from './ConnectionGate'; import Buffer from './buffers/ui/Buffer'; -import { upgradeAction } from '../store/actions'; interface State { connecting: boolean; @@ -25,8 +25,16 @@ export default class WeechatNative extends React.Component { connectionError: null }; + connectOnResume = true; + connection?: WeechatConnection; + appStateListener = AppState.addEventListener('change', (nextAppState) => { + if (nextAppState === 'active') { + this.onResume(); + } + }); + constructor(props: null) { super(props); store.dispatch( @@ -39,12 +47,17 @@ export default class WeechatNative extends React.Component { ); } + componentWillUnmount(): void { + this.appStateListener.remove(); + } + setNotificationToken = async (): Promise => { const token = await getPushNotificationStatusAsync(); if (token) this.sendMessageToBuffer('core.weechat', '/weechatrn ' + token); }; onConnectionSuccess = (connection: WeechatConnection): void => { + this.connectOnResume = true; this.setState({ connecting: false }); connection.send('(hotlist) hdata hotlist:gui_hotlist(*)'); connection.send( @@ -63,14 +76,15 @@ export default class WeechatNative extends React.Component { reconnect: boolean, connectionError: ConnectionError | null ): void => { - this.setState((state) => ({ + this.setState({ connecting: reconnect, - connectionError: state.connecting ? connectionError : null - })); + connectionError: reconnect ? null : connectionError + }); }; disconnect = (): void => { - this.connection && this.connection.close(); + this.connectOnResume = false; + this.connection && this.connection.disconnect(); }; onConnect = (hostname: string, password: string, ssl: boolean): void => { @@ -86,6 +100,13 @@ export default class WeechatNative extends React.Component { this.connection.connect(); }; + onResume = () => { + if (this.connectOnResume && this.connection?.isDisconnected()) { + this.setState({ connecting: true, connectionError: null }); + this.connection.connect(); + } + }; + fetchBufferInfo = ( bufferId: string, numLines = Buffer.DEFAULT_LINE_INCREMENT