Improve websocket behavior (#7976)

* Improve websocket behavior

* Revert uninteded changes

* Address feedback
This commit is contained in:
Daniel Espino García 2024-06-17 09:58:58 +02:00 committed by GitHub
parent dddd09aa9b
commit b694809da1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 162 additions and 189 deletions

View file

@ -22,7 +22,7 @@ import {getDeviceToken} from '@queries/app/global';
import {getChannelById, queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel';
import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry';
import {getHasCRTChanged} from '@queries/servers/preference';
import {getConfig, getCurrentChannelId, getCurrentTeamId, getIsDataRetentionEnabled, getPushVerificationStatus, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {getConfig, getCurrentChannelId, getCurrentTeamId, getIsDataRetentionEnabled, getPushVerificationStatus, getLastFullSync, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {deleteMyTeams, getAvailableTeamIds, getTeamChannelHistory, queryMyTeams, queryMyTeamsByIds, queryTeamsById} from '@queries/servers/team';
import NavigationStore from '@store/navigation_store';
import {isDMorGM, sortChannelsByDisplayName} from '@utils/channel';
@ -124,7 +124,7 @@ const entryRest = async (serverUrl: string, teamId?: string, channelId?: string,
}
const {database} = operator;
const lastDisconnectedAt = since || await getWebSocketLastDisconnected(database);
const lastDisconnectedAt = since || await getLastFullSync(database);
const fetchedData = await fetchAppEntryData(serverUrl, lastDisconnectedAt, teamId, channelId);
if ('error' in fetchedData) {

View file

@ -41,7 +41,6 @@ import {
} from '@calls/connection/websocket_event_handlers';
import {isSupportedServerCalls} from '@calls/utils';
import {Screens, WebsocketEvents} from '@constants';
import {SYSTEM_IDENTIFIERS} from '@constants/database';
import DatabaseManager from '@database/manager';
import AppsManager from '@managers/apps_manager';
import {getActiveServerUrl} from '@queries/app/servers';
@ -51,8 +50,8 @@ import {
getCurrentChannelId,
getCurrentTeamId,
getLicense,
getWebSocketLastDisconnected,
resetWebSocketLastDisconnected,
getLastFullSync,
setLastFullSync,
} from '@queries/servers/system';
import {getIsCRTEnabled} from '@queries/servers/thread';
import {getCurrentUser} from '@queries/servers/user';
@ -124,22 +123,6 @@ export async function handleReconnect(serverUrl: string) {
return doReconnect(serverUrl);
}
export async function handleClose(serverUrl: string, lastDisconnect: number) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
await operator.handleSystem({
systems: [
{
id: SYSTEM_IDENTIFIERS.WEBSOCKET,
value: lastDisconnect.toString(10),
},
],
prepareRecordsOnly: false,
});
}
async function doReconnect(serverUrl: string) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
@ -153,14 +136,14 @@ async function doReconnect(serverUrl: string) {
const {database} = operator;
const lastDisconnectedAt = await getWebSocketLastDisconnected(database);
resetWebSocketLastDisconnected(operator);
const lastFullSync = await getLastFullSync(database);
const now = Date.now();
const currentTeamId = await getCurrentTeamId(database);
const currentChannelId = await getCurrentChannelId(database);
setTeamLoading(serverUrl, true);
const entryData = await entry(serverUrl, currentTeamId, currentChannelId, lastDisconnectedAt);
const entryData = await entry(serverUrl, currentTeamId, currentChannelId, lastFullSync);
if ('error' in entryData) {
setTeamLoading(serverUrl, false);
return entryData.error;
@ -174,6 +157,8 @@ async function doReconnect(serverUrl: string) {
await operator.batchRecords(models, 'doReconnect');
}
await setLastFullSync(operator, now);
const tabletDevice = isTablet();
const isActiveServer = (await getActiveServerUrl()) === serverUrl;
if (isActiveServer && tabletDevice && initialChannelId === currentChannelId) {
@ -194,7 +179,7 @@ async function doReconnect(serverUrl: string) {
loadConfigAndCalls(serverUrl, currentUserId);
}
await deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId);
await deferredAppEntryActions(serverUrl, lastFullSync, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId);
openAllUnreadChannels(serverUrl);

View file

@ -15,56 +15,51 @@ const MAX_WEBSOCKET_FAILS = 7;
const WEBSOCKET_TIMEOUT = toMilliseconds({seconds: 30});
const MIN_WEBSOCKET_RETRY_TIME = toMilliseconds({seconds: 3});
const MAX_WEBSOCKET_RETRY_TIME = toMilliseconds({minutes: 5});
const DEFAULT_OPTIONS = {
forceConnection: true,
};
export default class WebSocketClient {
private conn?: WebSocketClientInterface;
private connectionTimeout: NodeJS.Timeout | undefined;
private connectionId: string;
private connectionId = '';
private token: string;
private stop = false;
private url = '';
private serverUrl: string;
private connectFailCount = 0;
// The first time we connect to a server (on init or login)
// we do the sync out of the websocket lifecycle.
// This is used to avoid calling twice to the sync logic.
private shouldSkipSync = false;
// responseSequence is the number to track a response sent
// via the websocket. A response will always have the same sequence number
// as the request.
private responseSequence: number;
private responseSequence = 1;
// serverSequence is the incrementing sequence number from the
// server-sent event stream.
private serverSequence: number;
private connectFailCount: number;
private serverSequence = 0;
// Callbacks
private eventCallback?: Function;
private firstConnectCallback?: () => void;
private missedEventsCallback?: () => void;
private reconnectCallback?: () => void;
private reliableReconnectCallback?: () => void;
private errorCallback?: Function;
private closeCallback?: (connectFailCount: number, lastDisconnect: number) => void;
private closeCallback?: (connectFailCount: number) => void;
private connectingCallback?: () => void;
private stop: boolean;
private lastConnect: number;
private lastDisconnect: number;
private url = '';
private serverUrl: string;
private hasReliablyReconnect = false;
constructor(serverUrl: string, token: string, lastDisconnect = 0) {
this.connectionId = '';
constructor(serverUrl: string, token: string) {
this.token = token;
this.responseSequence = 1;
this.serverSequence = 0;
this.connectFailCount = 0;
this.stop = false;
this.serverUrl = serverUrl;
this.lastConnect = 0;
this.lastDisconnect = lastDisconnect;
}
public async initialize(opts = {}) {
const defaults = {
forceConnection: true,
};
const {forceConnection} = Object.assign({}, defaults, opts);
public async initialize(opts = {}, shouldSkipSync = false) {
const {forceConnection} = Object.assign({}, DEFAULT_OPTIONS, opts);
if (forceConnection) {
this.stop = false;
@ -114,17 +109,19 @@ export default class WebSocketClient {
// Manually changing protocol since getOrCreateWebsocketClient does not accept http/s
if (this.url.startsWith('https:')) {
this.url = 'wss:' + this.url.substr('https:'.length);
this.url = 'wss:' + this.url.substring('https:'.length);
}
if (this.url.startsWith('http:')) {
this.url = 'ws:' + this.url.substr('http:'.length);
this.url = 'ws:' + this.url.substring('http:'.length);
}
if (this.connectFailCount === 0) {
logInfo('websocket connecting to ' + this.url);
}
this.shouldSkipSync = shouldSkipSync;
try {
const headers: ClientHeaders = {origin};
if (Platform.OS === 'android') {
@ -139,9 +136,7 @@ export default class WebSocketClient {
// In case turning on/off Wi-fi on Samsung devices
// the websocket will call onClose then onError then initialize again with readyState CLOSED, we need to open it again
if (this.conn.readyState === WebSocketReadyState.CLOSED) {
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
}
clearTimeout(this.connectionTimeout);
this.conn.open();
}
return;
@ -152,7 +147,7 @@ export default class WebSocketClient {
}
this.conn!.onOpen(() => {
this.lastConnect = Date.now();
clearTimeout(this.connectionTimeout);
// No need to reset sequence number here.
if (!reliableWebSockets) {
@ -165,34 +160,35 @@ export default class WebSocketClient {
this.sendMessage('authentication_challenge', {token: this.token});
}
if (this.connectFailCount > 0) {
if (this.shouldSkipSync) {
logInfo('websocket connected to', this.url);
this.firstConnectCallback?.();
} else {
logInfo('websocket re-established connection to', this.url);
if (!reliableWebSockets && this.reconnectCallback) {
this.reconnectCallback();
} else if (reliableWebSockets) {
// If a sync is needed, it is handled when receiving the HELLO websocket message
this.reliableReconnectCallback?.();
if (this.serverSequence && this.missedEventsCallback) {
this.missedEventsCallback();
}
this.hasReliablyReconnect = true;
}
} else if (this.firstConnectCallback) {
logInfo('websocket connected to', this.url);
this.firstConnectCallback();
}
this.connectFailCount = 0;
});
this.conn!.onClose(() => {
const now = Date.now();
if (this.lastDisconnect < this.lastConnect) {
this.lastDisconnect = now;
}
clearTimeout(this.connectionTimeout);
this.conn = undefined;
this.responseSequence = 1;
this.hasReliablyReconnect = false;
// We skip the sync on first connect, since we are syncing along
// the init logic. If the connection closes at any point after that,
// we don't want to skip the sync. If we keep the same connection and
// reliable websockets are enabled this won't trigger a new sync.
this.shouldSkipSync = false;
if (this.connectFailCount === 0) {
logInfo('websocket closed', this.url);
@ -201,7 +197,7 @@ export default class WebSocketClient {
this.connectFailCount++;
if (this.closeCallback) {
this.closeCallback(this.connectFailCount, this.lastDisconnect);
this.closeCallback(this.connectFailCount);
}
if (this.stop) {
@ -212,22 +208,13 @@ export default class WebSocketClient {
// If we've failed a bunch of connections then start backing off
if (this.connectFailCount > MAX_WEBSOCKET_FAILS) {
retryTime = MIN_WEBSOCKET_RETRY_TIME * this.connectFailCount;
if (retryTime > MAX_WEBSOCKET_RETRY_TIME) {
retryTime = MAX_WEBSOCKET_RETRY_TIME;
}
}
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
retryTime = Math.min(MIN_WEBSOCKET_RETRY_TIME * this.connectFailCount, MAX_WEBSOCKET_RETRY_TIME);
}
this.connectionTimeout = setTimeout(
() => {
if (this.stop) {
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
}
clearTimeout(this.connectionTimeout);
return;
}
this.initialize(opts);
@ -238,7 +225,6 @@ export default class WebSocketClient {
this.conn!.onError((evt: any) => {
if (evt.url === this.url) {
this.hasReliablyReconnect = false;
if (this.connectFailCount <= 1) {
logError('websocket error', this.url);
logError('WEBSOCKET ERROR EVENT', evt);
@ -263,7 +249,7 @@ export default class WebSocketClient {
} else if (this.eventCallback) {
if (reliableWebSockets) {
// We check the hello packet, which is always the first packet in a stream.
if (msg.event === WebsocketEvents.HELLO && this.reconnectCallback) {
if (msg.event === WebsocketEvents.HELLO) {
logInfo(this.url, 'got connection id ', msg.data.connection_id);
// If we already have a connectionId present, and server sends a different one,
@ -271,14 +257,16 @@ export default class WebSocketClient {
// If the server is not available the first time we try to connect, we won't have a connection id
// but still we need to sync.
// Then we do the sync calls, and reset sequence number to 0.
if (
(this.connectionId !== '' && this.connectionId !== msg.data.connection_id) ||
(this.hasReliablyReconnect && this.connectionId === '')
) {
logInfo(this.url, 'long timeout, or server restart, or sequence number is not found, or first connect after failure.');
this.reconnectCallback();
if (this.connectionId !== msg.data.connection_id) {
if (this.connectionId) {
logInfo(this.url, 'got a new connection due to long timeout, or server restart, or sequence number is not found');
} else {
logInfo(this.url, 'got the expected new connection id');
}
if (!this.shouldSkipSync) {
this.reconnectCallback?.();
}
this.serverSequence = 0;
this.hasReliablyReconnect = false;
}
// If it's a fresh connection, we have to set the connectionId regardless.
@ -290,16 +278,13 @@ export default class WebSocketClient {
// we just disconnect and reconnect.
if (msg.seq !== this.serverSequence) {
logInfo(this.url, 'missed websocket event, act_seq=' + msg.seq + ' exp_seq=' + this.serverSequence);
// We are not calling this.close() because we need to auto-restart.
this.connectFailCount = 0;
this.responseSequence = 1;
this.conn?.close(); // Will auto-reconnect after MIN_WEBSOCKET_RETRY_TIME.
this.connectionId = ''; // There was some problem with the sequence number, so we reset the connection
this.close(false); // Will auto-reconnect after MIN_WEBSOCKET_RETRY_TIME.
return;
}
} else if (msg.seq !== this.serverSequence && this.reconnectCallback) {
} else if (msg.seq !== this.serverSequence) {
logInfo(this.url, 'missed websocket event, act_seq=' + msg.seq + ' exp_seq=' + this.serverSequence);
this.reconnectCallback();
this.reconnectCallback?.();
}
this.serverSequence = msg.seq + 1;
@ -338,7 +323,7 @@ export default class WebSocketClient {
this.errorCallback = callback;
}
public setCloseCallback(callback: (connectFailCount: number, lastDisconnect: number) => void) {
public setCloseCallback(callback: (connectFailCount: number) => void) {
this.closeCallback = callback;
}
@ -346,14 +331,12 @@ export default class WebSocketClient {
this.stop = stop;
this.connectFailCount = 0;
this.responseSequence = 1;
this.hasReliablyReconnect = false;
if (this.conn && this.conn.readyState === WebSocketReadyState.OPEN) {
this.conn.close();
}
clearTimeout(this.connectionTimeout);
this.conn?.close();
}
public invalidate() {
clearTimeout(this.connectionTimeout);
this.conn?.invalidate();
this.conn = undefined;
}
@ -381,6 +364,6 @@ export default class WebSocketClient {
}
public isConnected(): boolean {
return this.conn?.readyState === WebSocketReadyState.OPEN; //|| (!this.stop && this.connectFailCount <= 2);
return this.conn?.readyState === WebSocketReadyState.OPEN;
}
}

View file

@ -1,16 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import NetInfo, {type NetInfoState} from '@react-native-community/netinfo';
import {debounce, type DebouncedFunc} from 'lodash';
import {AppState, type AppStateStatus} from 'react-native';
import NetInfo, {type NetInfoState, type NetInfoSubscription} from '@react-native-community/netinfo';
import {AppState, type AppStateStatus, type NativeEventSubscription} from 'react-native';
import BackgroundTimer from 'react-native-background-timer';
import {BehaviorSubject} from 'rxjs';
import {distinctUntilChanged} from 'rxjs/operators';
import {setCurrentUserStatus} from '@actions/local/user';
import {fetchStatusByIds} from '@actions/remote/user';
import {handleClose, handleEvent, handleFirstConnect, handleReconnect} from '@actions/websocket';
import {handleEvent, handleFirstConnect, handleReconnect} from '@actions/websocket';
import WebSocketClient from '@client/websocket';
import {General} from '@constants';
import DatabaseManager from '@database/manager';
@ -27,7 +26,7 @@ class WebsocketManager {
private connectedSubjects: {[serverUrl: string]: BehaviorSubject<WebsocketConnectedState>} = {};
private clients: Record<string, WebSocketClient> = {};
private connectionTimerIDs: Record<string, DebouncedFunc<() => void>> = {};
private connectionTimerIDs: Record<string, NodeJS.Timeout> = {};
private isBackgroundTimerRunning = false;
private netConnected = false;
private previousActiveState: boolean;
@ -35,6 +34,9 @@ class WebsocketManager {
private backgroundIntervalId: number | undefined;
private firstConnectionSynced: Record<string, boolean> = {};
private appStateSubscription: NativeEventSubscription | undefined;
private netStateSubscription: NetInfoSubscription | undefined;
constructor() {
this.previousActiveState = AppState.currentState === 'active';
}
@ -45,36 +47,41 @@ class WebsocketManager {
({serverUrl, token}) => {
try {
DatabaseManager.getServerDatabaseAndOperator(serverUrl);
this.createClient(serverUrl, token, 0);
this.createClient(serverUrl, token);
} catch (error) {
logError('WebsocketManager init error', error);
}
},
);
AppState.addEventListener('change', this.onAppStateChange);
NetInfo.addEventListener(this.onNetStateChange);
this.appStateSubscription?.remove();
this.netStateSubscription?.();
this.appStateSubscription = AppState.addEventListener('change', this.onAppStateChange);
this.netStateSubscription = NetInfo.addEventListener(this.onNetStateChange);
};
public invalidateClient = (serverUrl: string) => {
this.clients[serverUrl]?.close();
this.clients[serverUrl]?.close(true);
this.clients[serverUrl]?.invalidate();
if (this.connectionTimerIDs[serverUrl]) {
this.connectionTimerIDs[serverUrl].cancel();
}
clearTimeout(this.connectionTimerIDs[serverUrl]);
delete this.clients[serverUrl];
delete this.firstConnectionSynced[serverUrl];
// We don't remove the connected subject so any potential client invalidation
// and subsequent creation of the client can still be observed by the component.
// Being purist, this is a memory leak, since we never clean any server url,
// but since this information lives in memory and we don't expect many servers
// to be added and removed in one single session, this should be fine.
this.getConnectedSubject(serverUrl).next('not_connected');
delete this.connectedSubjects[serverUrl];
};
public createClient = (serverUrl: string, bearerToken: string, storedLastDisconnect = 0) => {
public createClient = (serverUrl: string, bearerToken: string) => {
if (this.clients[serverUrl]) {
this.invalidateClient(serverUrl);
}
const client = new WebSocketClient(serverUrl, bearerToken, storedLastDisconnect);
const client = new WebSocketClient(serverUrl, bearerToken);
client.setFirstConnectCallback(() => this.onFirstConnect(serverUrl));
client.setEventCallback((evt: any) => handleEvent(serverUrl, evt));
@ -82,7 +89,7 @@ class WebsocketManager {
//client.setMissedEventsCallback(() => {}) Nothing to do on missedEvents callback
client.setReconnectCallback(() => this.onReconnect(serverUrl));
client.setReliableReconnectCallback(() => this.onReliableReconnect(serverUrl));
client.setCloseCallback((connectFailCount: number, lastDisconnect: number) => this.onWebsocketClose(serverUrl, connectFailCount, lastDisconnect));
client.setCloseCallback((connectFailCount: number) => this.onWebsocketClose(serverUrl, connectFailCount));
this.clients[serverUrl] = client;
@ -92,23 +99,21 @@ class WebsocketManager {
public closeAll = () => {
for (const url of Object.keys(this.clients)) {
const client = this.clients[url];
if (client.isConnected()) {
client.close(true);
this.getConnectedSubject(url).next('not_connected');
}
client.close(true);
this.getConnectedSubject(url).next('not_connected');
}
};
public openAll = async () => {
let queued = 0;
for await (const clientUrl of Object.keys(this.clients)) {
const activeServerUrl = await DatabaseManager.getActiveServerUrl();
if (clientUrl === activeServerUrl) {
this.initializeClient(clientUrl);
} else {
queued += 1;
this.getConnectedSubject(clientUrl).next('connecting');
const bounce = debounce(this.initializeClient.bind(this, clientUrl), WAIT_UNTIL_NEXT);
this.connectionTimerIDs[clientUrl] = bounce;
bounce();
this.connectionTimerIDs[clientUrl] = setTimeout(() => this.initializeClient(clientUrl), WAIT_UNTIL_NEXT * queued);
}
}
};
@ -131,24 +136,24 @@ class WebsocketManager {
return this.connectedSubjects[serverUrl];
};
private cancelAllConnections = () => {
for (const url in this.connectionTimerIDs) {
if (this.connectionTimerIDs[url]) {
this.connectionTimerIDs[url].cancel();
delete this.connectionTimerIDs[url];
}
private cancelConnectTimers = () => {
for (const [url, timer] of Object.entries(this.connectionTimerIDs)) {
clearTimeout(timer);
delete this.connectionTimerIDs[url];
}
};
public initializeClient = async (serverUrl: string) => {
const client: WebSocketClient = this.clients[serverUrl];
this.connectionTimerIDs[serverUrl]?.cancel();
clearTimeout(this.connectionTimerIDs[serverUrl]);
delete this.connectionTimerIDs[serverUrl];
if (!client?.isConnected()) {
client.initialize();
if (!this.firstConnectionSynced[serverUrl]) {
const hasSynced = this.firstConnectionSynced[serverUrl];
client.initialize({}, !hasSynced);
if (!hasSynced) {
const error = await handleFirstConnect(serverUrl);
if (error) {
// This will try to reconnect and try to sync again
client.close(false);
}
@ -178,12 +183,10 @@ class WebsocketManager {
this.getConnectedSubject(serverUrl).next('connected');
};
private onWebsocketClose = async (serverUrl: string, connectFailCount: number, lastDisconnect: number) => {
private onWebsocketClose = async (serverUrl: string, connectFailCount: number) => {
this.getConnectedSubject(serverUrl).next('not_connected');
if (connectFailCount <= 1) { // First fail
await setCurrentUserStatus(serverUrl, General.OFFLINE);
await handleClose(serverUrl, lastDisconnect);
this.stopPeriodicStatusUpdates(serverUrl);
}
};
@ -212,65 +215,66 @@ class WebsocketManager {
}
private stopPeriodicStatusUpdates(serverUrl: string) {
const currentId = this.statusUpdatesIntervalIDs[serverUrl];
if (currentId != null) {
clearInterval(currentId);
}
clearInterval(this.statusUpdatesIntervalIDs[serverUrl]);
delete this.statusUpdatesIntervalIDs[serverUrl];
}
private onAppStateChange = async (appState: AppStateStatus) => {
const isActive = appState === 'active';
if (isActive === this.previousActiveState) {
return;
}
private onAppStateChange = (appState: AppStateStatus) => {
const isMain = isMainActivity();
this.cancelAllConnections();
if (!isActive && !this.isBackgroundTimerRunning) {
this.isBackgroundTimerRunning = true;
this.cancelAllConnections();
this.backgroundIntervalId = BackgroundTimer.setInterval(() => {
this.closeAll();
BackgroundTimer.clearInterval(this.backgroundIntervalId!);
this.isBackgroundTimerRunning = false;
}, WAIT_TO_CLOSE);
this.previousActiveState = isActive;
if (!isMain) {
return;
}
if (isActive && this.netConnected && isMain) { // Reopen the websockets only if there is connection
if (this.backgroundIntervalId) {
BackgroundTimer.clearInterval(this.backgroundIntervalId);
}
this.isBackgroundTimerRunning = false;
this.openAll();
this.previousActiveState = isActive;
return;
}
if (isMain) {
this.previousActiveState = isActive;
}
const isActive = appState === 'active';
this.handleStateChange(this.netConnected, isActive);
};
private onNetStateChange = async (netState: NetInfoState) => {
private onNetStateChange = (netState: NetInfoState) => {
const newState = Boolean(netState.isConnected);
if (this.netConnected === newState) {
return;
}
this.netConnected = newState;
this.handleStateChange(newState, this.previousActiveState);
};
if (this.netConnected && this.previousActiveState) { // Reopen the websockets only if the app is active
this.openAll();
private handleStateChange = (currentIsConnected: boolean, currentIsActive: boolean) => {
if (currentIsActive === this.previousActiveState && currentIsConnected === this.netConnected) {
return;
}
this.closeAll();
this.cancelConnectTimers();
const wentBackground = this.previousActiveState && !currentIsActive;
this.previousActiveState = currentIsActive;
this.netConnected = currentIsConnected;
if (!currentIsConnected) {
this.closeAll();
return;
}
if (currentIsActive) {
if (this.isBackgroundTimerRunning) {
BackgroundTimer.clearInterval(this.backgroundIntervalId!);
}
this.isBackgroundTimerRunning = false;
if (this.netConnected) {
this.openAll();
}
return;
}
if (wentBackground && !this.isBackgroundTimerRunning) {
this.isBackgroundTimerRunning = true;
this.backgroundIntervalId = BackgroundTimer.setInterval(() => {
this.closeAll();
BackgroundTimer.clearInterval(this.backgroundIntervalId!);
this.isBackgroundTimerRunning = false;
}, WAIT_TO_CLOSE);
}
};
public getClient = (serverUrl: string): WebSocketClient | undefined => {

View file

@ -7,7 +7,7 @@ import DatabaseManager from '@database/manager';
import {prepareCategoriesAndCategoriesChannels} from './categories';
import {prepareDeleteChannel, prepareMyChannelsForTeam} from './channel';
import {prepareMyPreferences} from './preference';
import {resetWebSocketLastDisconnected} from './system';
import {resetLastFullSync} from './system';
import {prepareDeleteTeam, prepareMyTeams} from './team';
import {prepareUsers} from './user';
@ -103,7 +103,7 @@ export async function truncateCrtRelatedTables(serverUrl: string): Promise<{erro
],
});
});
await resetWebSocketLastDisconnected(operator);
await resetLastFullSync(operator);
} catch (error) {
if (__DEV__) {
throw error;

View file

@ -311,7 +311,7 @@ export const observeRecentCustomStatus = (database: Database): Observable<UserCu
);
};
export const getWebSocketLastDisconnected = async (serverDatabase: Database) => {
export const getLastFullSync = async (serverDatabase: Database) => {
try {
const websocketLastDisconnected = await serverDatabase.get<SystemModel>(SYSTEM).find(SYSTEM_IDENTIFIERS.WEBSOCKET);
return (parseInt(websocketLastDisconnected?.value || 0, 10) || 0);
@ -320,16 +320,17 @@ export const getWebSocketLastDisconnected = async (serverDatabase: Database) =>
}
};
export const observeWebsocketLastDisconnected = (database: Database) => {
return querySystemValue(database, SYSTEM_IDENTIFIERS.WEBSOCKET).observe().pipe(
switchMap((result) => (result.length ? result[0].observe() : of$({value: '0'}))),
switchMap((model) => of$(parseInt(model.value || 0, 10) || 0)),
);
export const setLastFullSync = async (operator: ServerDataOperator, value: number, prepareRecordsOnly = false) => {
return operator.handleSystem({systems: [{
id: SYSTEM_IDENTIFIERS.WEBSOCKET,
value,
}],
prepareRecordsOnly});
};
export const resetWebSocketLastDisconnected = async (operator: ServerDataOperator, prepareRecordsOnly = false) => {
export const resetLastFullSync = async (operator: ServerDataOperator, prepareRecordsOnly = false) => {
const {database} = operator;
const lastDisconnectedAt = await getWebSocketLastDisconnected(database);
const lastDisconnectedAt = await getLastFullSync(database);
if (lastDisconnectedAt) {
return operator.handleSystem({systems: [{