Skip to content
Snippets Groups Projects
Unverified Commit de90504a authored by Matiss Janis Aboltins's avatar Matiss Janis Aboltins Committed by GitHub
Browse files

:bug: (electron) reconnect to sockets if connection lost (#1694)

parent f6e2d3b1
No related branches found
No related tags found
No related merge requests found
......@@ -38,6 +38,7 @@ require('./security');
const { fork } = require('child_process');
const path = require('path');
const http = require('http');
require('./setRequireHook');
......@@ -55,10 +56,8 @@ const WindowState = require('./window-state.js');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let clientWin;
let serverWin; // eslint-disable-line @typescript-eslint/no-unused-vars
let serverProcess;
let serverSocket;
let IS_QUITTING = false;
updater.onEvent((type, data) => {
// Notify both the app and the about window
......@@ -98,9 +97,48 @@ function createBackgroundProcess(socketName) {
console.log('Unknown server message: ' + msg.type);
}
});
return serverProcess;
}
const isPortFree = port =>
new Promise(resolve => {
const server = http
.createServer()
.listen(port, () => {
server.close();
resolve(true);
})
.on('error', () => {
resolve(false);
});
});
async function createSocketConnection() {
if (!serverSocket) serverSocket = await getRandomPort();
// Spawn the child process if it is not already running
// (sometimes long child processes die, so we need to set them
// up again)
const isFree = await isPortFree(serverSocket);
if (isFree) {
await createBackgroundProcess(serverSocket);
}
if (!clientWin) {
return;
}
// Send a heartbeat to the client whenever we attempt to create a new
// sockets connection
clientWin.webContents.executeJavaScript(
`window.__actionsForMenu && window.__actionsForMenu.reconnect(${serverSocket})`,
);
}
async function createWindow() {
await createSocketConnection();
const windowState = await WindowState.get();
// Create the browser window.
......@@ -137,15 +175,6 @@ async function createWindow() {
win.loadURL(`app://actual/`);
}
win.on('close', () => {
// We don't want to close the budget on exit because that will
// clear the state which re-opens the last budget automatically on
// startup
if (!IS_QUITTING) {
clientWin.webContents.executeJavaScript('__actionsForMenu.closeBudget()');
}
});
win.on('closed', () => {
clientWin = null;
updateMenu(false);
......@@ -233,8 +262,6 @@ function updateMenu(isBudgetOpen) {
app.setAppUserModelId('com.shiftreset.actual');
app.on('ready', async () => {
serverSocket = await getRandomPort();
// Install an `app://` protocol that always returns the base HTML
// file no matter what URL it is. This allows us to use react-router
// on the frontend
......@@ -277,8 +304,6 @@ app.on('ready', async () => {
require('electron').powerMonitor.on('suspend', () => {
console.log('Suspending', new Date());
});
createBackgroundProcess(serverSocket);
});
app.on('window-all-closed', () => {
......@@ -289,7 +314,6 @@ app.on('window-all-closed', () => {
});
app.on('before-quit', () => {
IS_QUITTING = true;
if (serverProcess) {
serverProcess.kill();
serverProcess = null;
......@@ -302,6 +326,14 @@ app.on('activate', () => {
}
});
app.on('did-become-active', () => {
// Reconnect whenever the window becomes active;
// We don't know what might have happened in-between, so it's better
// to be safe than sorry; the client can then decide if it wants to
// reconnect or not.
createSocketConnection();
});
ipcMain.on('get-bootstrap-data', event => {
event.returnValue = {
version: app.getVersion(),
......
import { send } from '../../platform/client/fetch';
import { init as initConnection, send } from '../../platform/client/fetch';
import * as constants from '../constants';
import type {
AppState,
......@@ -15,6 +15,12 @@ export function setAppState(state: Partial<AppState>): SetAppStateAction {
};
}
export function reconnect(connectionName: string) {
return () => {
initConnection(connectionName);
};
}
export function updateApp() {
return async (dispatch: Dispatch) => {
global.Actual.applyAppUpdate();
......
......@@ -8,9 +8,17 @@ let replyHandlers = new Map();
let listeners = new Map();
let messageQueue = [];
let socketClient = null;
let activePort = null;
function connectSocket(port, onOpen) {
// Do nothing if connection to this port is already active
if (socketClient && port === activePort) {
return;
}
let client = new WebSocket('ws://localhost:' + port);
socketClient = client;
activePort = port;
client.onmessage = event => {
const msg = JSON.parse(event.data);
......@@ -62,7 +70,6 @@ function connectSocket(port, onOpen) {
};
client.onopen = event => {
socketClient = client;
// Send any messages that were queued while closed
if (messageQueue.length > 0) {
messageQueue.forEach(msg => {
......@@ -73,10 +80,13 @@ function connectSocket(port, onOpen) {
onOpen();
};
client.onclose = () => {
socketClient = null;
};
}
export const init: T.Init = async function (socketName) {
await clearServer();
return new Promise(resolve => connectSocket(socketName, resolve));
};
......
---
category: Bugfix
authors: [MatissJanis]
---
Desktop: reconnect to websockets if connection lost or server restarted
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment