forked from Ivasoft/mattermost-mobile
Gekidou websockets (#5671)
* Adapt websocket client * Add Websocket Manager * Address feedback * Start websockets on init and login, and invalidate on logout * Add temporal logging and bug fixing * Working WS * Add reconnect actions and periodic updates * Address feedback * Add missing change * Several improvements on websocket reconnect and channel handling * fix gekidou package-lock.json * update Podfile.lock * Address feedback * Address feedback * Address feedback * Fix update channel delete at * Catch errors on batchRecords * Update app/queries/servers/channel.ts Co-authored-by: Elias Nahum <nahumhbl@gmail.com> Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
committed by
GitHub
parent
e6f9b0e258
commit
d1e0c99c3d
@@ -15,6 +15,7 @@ import {getServerCredentials, removeServerCredentials} from '@init/credentials';
|
||||
import {getLaunchPropsFromDeepLink, relaunchApp} from '@init/launch';
|
||||
import NetworkManager from '@init/network_manager';
|
||||
import PushNotifications from '@init/push_notifications';
|
||||
import WebsocketManager from '@init/websocket_manager';
|
||||
import {queryCurrentUser} from '@queries/servers/user';
|
||||
import {LaunchType} from '@typings/launch';
|
||||
import {deleteFileCache} from '@utils/file';
|
||||
@@ -84,8 +85,8 @@ class GlobalEventHandler {
|
||||
onLogout = async (serverUrl: string) => {
|
||||
await removeServerCredentials(serverUrl);
|
||||
|
||||
// TODO WebSocket: invalidate WebSocket client
|
||||
NetworkManager.invalidateClient(serverUrl);
|
||||
WebsocketManager.invalidateClient(serverUrl);
|
||||
await DatabaseManager.deleteServerDatabase(serverUrl);
|
||||
|
||||
const analyticsClient = analytics.get(serverUrl);
|
||||
|
||||
184
app/init/websocket_manager.ts
Normal file
184
app/init/websocket_manager.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import NetInfo, {NetInfoState} from '@react-native-community/netinfo';
|
||||
import {AppState, AppStateStatus} from 'react-native';
|
||||
|
||||
import {setCurrentUserStatusOffline} from '@actions/local/user';
|
||||
import {fetchStatusByIds} from '@actions/remote/user';
|
||||
import {handleClose, handleEvent, handleFirstConnect, handleReconnect} from '@actions/websocket';
|
||||
import WebSocketClient from '@app/client/websocket';
|
||||
import {General} from '@app/constants';
|
||||
import {queryWebSocketLastDisconnected} from '@app/queries/servers/system';
|
||||
import {queryAllUsers} from '@app/queries/servers/user';
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
||||
import type {ServerCredential} from '@typings/credentials';
|
||||
|
||||
class WebsocketManager {
|
||||
private clients: Record<string, WebSocketClient> = {};
|
||||
private statusUpdatesIntervalIDs: Record<string, NodeJS.Timer> = {};
|
||||
private previousAppState: AppStateStatus;
|
||||
private netConnected = false;
|
||||
|
||||
constructor() {
|
||||
this.previousAppState = AppState.currentState;
|
||||
}
|
||||
|
||||
public init = async (serverCredentials: ServerCredential[]) => {
|
||||
this.netConnected = Boolean((await NetInfo.fetch()).isConnected);
|
||||
await Promise.all(
|
||||
serverCredentials.map(
|
||||
async ({serverUrl, token}) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
const lastDisconnect = await queryWebSocketLastDisconnected(database);
|
||||
try {
|
||||
this.createClient(serverUrl, token, lastDisconnect);
|
||||
} catch (error) {
|
||||
console.log('WebsocketManager init error', error); //eslint-disable-line no-console
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
AppState.addEventListener('change', this.onAppStateChange);
|
||||
NetInfo.addEventListener(this.onNetStateChange);
|
||||
}
|
||||
|
||||
public invalidateClient = (serverUrl: string) => {
|
||||
this.clients[serverUrl]?.close();
|
||||
this.clients[serverUrl]?.invalidate();
|
||||
delete this.clients[serverUrl];
|
||||
}
|
||||
|
||||
public createClient = (serverUrl: string, bearerToken: string, storedLastDisconnect = 0) => {
|
||||
const client = new WebSocketClient(serverUrl, bearerToken, storedLastDisconnect);
|
||||
|
||||
client.setFirstConnectCallback(() => this.onFirstConnect(serverUrl));
|
||||
client.setEventCallback((evt: any) => handleEvent(serverUrl, evt));
|
||||
|
||||
//client.setMissedEventsCallback(() => {}) Nothing to do on missedEvents callback
|
||||
client.setReconnectCallback(() => this.onReconnect(serverUrl));
|
||||
client.setCloseCallback((connectFailCount: number, lastDisconnect: number) => this.onWebsocketClose(serverUrl, connectFailCount, lastDisconnect));
|
||||
|
||||
if (this.netConnected) {
|
||||
client.initialize();
|
||||
}
|
||||
this.clients[serverUrl] = client;
|
||||
|
||||
return this.clients[serverUrl];
|
||||
}
|
||||
|
||||
public closeAll = () => {
|
||||
for (const client of Object.values(this.clients)) {
|
||||
client.close(true);
|
||||
}
|
||||
}
|
||||
|
||||
public openAll = () => {
|
||||
for (const client of Object.values(this.clients)) {
|
||||
if (!client.isConnected()) {
|
||||
client.initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public isConnected = (serverUrl: string): boolean => {
|
||||
return this.clients[serverUrl]?.isConnected();
|
||||
}
|
||||
|
||||
private onFirstConnect = (serverUrl: string) => {
|
||||
this.startPeriodicStatusUpdates(serverUrl);
|
||||
handleFirstConnect(serverUrl);
|
||||
}
|
||||
|
||||
private onReconnect = (serverUrl: string) => {
|
||||
this.startPeriodicStatusUpdates(serverUrl);
|
||||
handleReconnect(serverUrl);
|
||||
}
|
||||
|
||||
private onWebsocketClose = async (serverUrl: string, connectFailCount: number, lastDisconnect: number) => {
|
||||
if (connectFailCount <= 1) { // First fail
|
||||
await setCurrentUserStatusOffline(serverUrl);
|
||||
await handleClose(serverUrl, lastDisconnect);
|
||||
|
||||
this.stopPeriodicStatusUpdates(serverUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private startPeriodicStatusUpdates(serverUrl: string) {
|
||||
let currentId = this.statusUpdatesIntervalIDs[serverUrl];
|
||||
if (currentId != null) {
|
||||
clearInterval(currentId);
|
||||
}
|
||||
|
||||
const getStatusForUsers = async () => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
|
||||
const users = await queryAllUsers(database.database);
|
||||
|
||||
const userIds = users.map((u) => u.id);
|
||||
if (!userIds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetchStatusByIds(serverUrl, userIds);
|
||||
};
|
||||
|
||||
currentId = setInterval(getStatusForUsers, General.STATUS_INTERVAL);
|
||||
this.statusUpdatesIntervalIDs[serverUrl] = currentId;
|
||||
}
|
||||
|
||||
private stopPeriodicStatusUpdates(serverUrl: string) {
|
||||
const currentId = this.statusUpdatesIntervalIDs[serverUrl];
|
||||
if (currentId != null) {
|
||||
clearInterval(currentId);
|
||||
}
|
||||
|
||||
delete this.statusUpdatesIntervalIDs[serverUrl];
|
||||
}
|
||||
|
||||
private onAppStateChange = async (appState: AppStateStatus) => {
|
||||
if (appState === this.previousAppState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (appState !== 'active') {
|
||||
this.closeAll();
|
||||
this.previousAppState = appState;
|
||||
return;
|
||||
}
|
||||
|
||||
if (appState === 'active' && this.netConnected) { // Reopen the websockets only if there is connection
|
||||
this.openAll();
|
||||
this.previousAppState = appState;
|
||||
return;
|
||||
}
|
||||
|
||||
this.previousAppState = appState;
|
||||
}
|
||||
|
||||
private onNetStateChange = async (netState: NetInfoState) => {
|
||||
const newState = Boolean(netState.isConnected);
|
||||
if (this.netConnected === newState) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.netConnected = newState;
|
||||
|
||||
if (this.netConnected && this.previousAppState === 'active') { // Reopen the websockets only if the app is active
|
||||
this.openAll();
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeAll();
|
||||
}
|
||||
}
|
||||
|
||||
export default new WebsocketManager();
|
||||
Reference in New Issue
Block a user