Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] :electron: hosting the sync-server with the desktop app (POC) #3631

Draft
wants to merge 75 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
20e2ef2
start to this epic idea
MikesGlitch Oct 10, 2024
361200e
bits
MikesGlitch Oct 10, 2024
c718b2c
add a note
MikesGlitch Oct 10, 2024
e32fb8b
server starting button erroring on imports due to env differences
MikesGlitch Oct 11, 2024
1802818
working now
MikesGlitch Oct 11, 2024
194f50d
using node_modules and allowing electron-builder to bundle it
MikesGlitch Oct 12, 2024
7782f2d
ensuring deps are built before running server
MikesGlitch Oct 12, 2024
55e9113
unneeded mode
MikesGlitch Oct 12, 2024
3266319
exposing sync server via ngrok
MikesGlitch Oct 13, 2024
b3e6d28
log msg for dev
MikesGlitch Oct 13, 2024
ce5225e
using official ngrok package for smaller size
MikesGlitch Oct 14, 2024
07e7730
comment
MikesGlitch Oct 14, 2024
b9c14dc
ngrok bits
MikesGlitch Oct 14, 2024
c6d2f59
correcting ngrok conditional logic
MikesGlitch Oct 14, 2024
8eb145f
ngrok settings optional
MikesGlitch Oct 14, 2024
23fdf04
bits
MikesGlitch Oct 14, 2024
58aa350
moving server folders
MikesGlitch Oct 15, 2024
2dc34de
package process
MikesGlitch Oct 15, 2024
4873c9c
merge
MikesGlitch Oct 15, 2024
5d01674
what an ordeal
MikesGlitch Oct 15, 2024
8eca643
fixes
MikesGlitch Oct 17, 2024
b83a3c6
reenable sync server now tests have passed
MikesGlitch Oct 17, 2024
39d060b
splitting up server management to allow config of desktop app server …
MikesGlitch Oct 18, 2024
45d9fae
fix the port thing
MikesGlitch Oct 20, 2024
0a333ed
tunnel erorrs cleanup
MikesGlitch Oct 20, 2024
a78ad88
trickle in
MikesGlitch Oct 20, 2024
804425f
resetting state if not available to avoid hard crashes
MikesGlitch Oct 21, 2024
a1de454
small updates until I figure out what the issue is
MikesGlitch Oct 21, 2024
e1c5a68
merge
MikesGlitch Oct 22, 2024
1178bec
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Oct 23, 2024
17f520f
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Oct 24, 2024
6d0363f
settings
MikesGlitch Oct 27, 2024
7d5af1f
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Oct 27, 2024
f3c2743
remove it
MikesGlitch Oct 27, 2024
76b2918
thoughts
MikesGlitch Oct 27, 2024
9ff112c
waiting on sync server before starting
MikesGlitch Oct 28, 2024
312e85e
fix timeout message
MikesGlitch Oct 30, 2024
4f667d0
note
MikesGlitch Oct 30, 2024
a5f2f8b
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 3, 2024
79f648f
use env variable instead of https agent for default fetch
MikesGlitch Nov 4, 2024
2265164
updating root ca impl to use node env variable for more support
MikesGlitch Nov 4, 2024
4400ff1
release notes
MikesGlitch Nov 4, 2024
98174bb
removing node-fetch
MikesGlitch Nov 4, 2024
26adfec
clean up
MikesGlitch Nov 4, 2024
8cfd39f
error message
MikesGlitch Nov 4, 2024
4dd1fe0
merg
MikesGlitch Nov 4, 2024
78fbebe
adding the sync server into the workspace as an experiment
MikesGlitch Nov 4, 2024
5886678
merge
MikesGlitch Nov 4, 2024
6513207
update server
MikesGlitch Nov 4, 2024
99b6cb2
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 7, 2024
4036b57
responsive
MikesGlitch Nov 7, 2024
c718e67
test
MikesGlitch Nov 7, 2024
2a1bb2c
put it back
MikesGlitch Nov 7, 2024
8847f64
fix rootpath
MikesGlitch Nov 9, 2024
0ce8c57
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 11, 2024
8037e03
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 12, 2024
6671e2c
merge
MikesGlitch Nov 12, 2024
207d63e
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 14, 2024
b539e7b
merge
MikesGlitch Nov 19, 2024
99a97c6
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 19, 2024
2656d65
bits
MikesGlitch Nov 20, 2024
3f70a9f
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 21, 2024
64b007f
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 22, 2024
3ada82a
update server
MikesGlitch Nov 23, 2024
53f1054
yarn lock
MikesGlitch Nov 23, 2024
2d27e1d
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 24, 2024
f14a118
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Nov 30, 2024
3e3d1cf
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Dec 2, 2024
862c765
Merge branch 'master' of https://github.com/MikesGlitch/actual into e…
MikesGlitch Dec 6, 2024
c2aad50
updated server
MikesGlitch Dec 6, 2024
606fe7e
update the lock
MikesGlitch Dec 6, 2024
851f1ef
merge
MikesGlitch Dec 14, 2024
f57dac2
merge
MikesGlitch Dec 23, 2024
a2db19b
merge
MikesGlitch Jan 10, 2025
626c72a
fallout
MikesGlitch Jan 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/package-electron
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fi
yarn workspace loot-core build:node

yarn workspace @actual-app/web build --mode=desktop
yarn workspace @actual-app/crdt build
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crdt is a dependency of actual-server. Build it before packaging the sync-server


yarn workspace desktop-electron update-client

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"start": "yarn start:browser",
"start:desktop": "yarn rebuild-electron && npm-run-all --parallel 'start:desktop-*'",
"start:desktop-node": "yarn workspace loot-core watch:node",
"start:desktop-server-deps": "yarn workspace @actual-app/crdt build",
"start:desktop-client": "yarn workspace @actual-app/web watch",
"start:desktop-electron": "yarn workspace desktop-electron watch",
"start:electron": "yarn start:desktop",
Expand Down
3 changes: 3 additions & 0 deletions packages/desktop-client/src/browser-preload.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ global.Actual = {
openURLInBrowser: url => {
window.open(url, '_blank');
},
downloadActualServer: () => {},
startActualServer: () => {},
exposeActualServer: () => {},
onEventFromMain: () => {},
applyAppUpdate: () => {},
updateAppMenu: () => {},
Expand Down
35 changes: 35 additions & 0 deletions packages/desktop-client/src/components/manager/BudgetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
type SyncedLocalFile,
} from 'loot-core/types/file';

import { useGlobalPref } from '../../hooks/useGlobalPref';
import { useInitialMount } from '../../hooks/useInitialMount';
import { useMetadataPref } from '../../hooks/useMetadataPref';
import { AnimatedLoading } from '../../icons/AnimatedLoading';
Expand Down Expand Up @@ -441,6 +442,20 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) {
}
};

const startActualServer = async () => {
await globalThis.Actual.startActualServer('v24.10.1');
};

const [ngrokConfig] = useGlobalPref('ngrokConfig');
const exposeActualServer = async () => {
const url = await globalThis.Actual.exposeActualServer({
authToken: ngrokConfig.authToken,
port: ngrokConfig.port,
domain: ngrokConfig.domain,
});
console.info('exposing actual at: ' + url);
};

return (
<View
style={{
Expand Down Expand Up @@ -515,6 +530,26 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) {
<Trans>Create test file</Trans>
</Button>
)}
<Button
variant="primary"
onPress={startActualServer}
style={{
...narrowButtonStyle,
marginLeft: 10,
}}
>
Start Server
</Button>
<Button
variant="primary"
onPress={exposeActualServer}
style={{
...narrowButtonStyle,
marginLeft: 10,
}}
>
Expose Server
</Button>
</View>
)}
</View>
Expand Down
96 changes: 96 additions & 0 deletions packages/desktop-electron/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';

import ngrok from '@ngrok/ngrok';
import {
net,
app,
Expand All @@ -19,6 +20,8 @@ import {
import { copy, exists, remove } from 'fs-extra';
import promiseRetry from 'promise-retry';

import { GlobalPrefs } from 'loot-core/types/prefs';

import { getMenu } from './menu';
import {
get as getWindowState,
Expand Down Expand Up @@ -51,11 +54,29 @@ if (!isDev || !process.env.ACTUAL_DATA_DIR) {
// be closed automatically when the JavaScript object is garbage collected.
let clientWin: BrowserWindow | null;
let serverProcess: UtilityProcess | null;
let actualServerProcess: UtilityProcess | null;
let globalPrefs: Partial<GlobalPrefs> | null;

if (isDev) {
process.traceProcessWarnings = true;
}

async function loadGlobalPrefs() {
let state: GlobalPrefs | undefined = undefined;
try {
state = JSON.parse(
fs.readFileSync(
path.join(process.env.ACTUAL_DATA_DIR, 'global-store.json'),
'utf8',
),
);
} catch (e) {
console.log('Could not load global state');
}

return state;
}

function createBackgroundProcess() {
serverProcess = utilityProcess.fork(
__dirname + '/server.js',
Expand Down Expand Up @@ -93,6 +114,65 @@ function createBackgroundProcess() {
});
}

function startSyncServer() {
const serverPath = path.resolve(
__dirname,
'../../../node_modules/actual-sync/app.js', // if letting electron-builder bundle it (needs to be in our workspace)
);

// NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix?
// Or we can override the config.json location when starting the process
try {
actualServerProcess = utilityProcess.fork(
serverPath, // This requires actual-server depencies (crdt) to be built before running electron - they need to be manually specified because actual-server doesn't get bundled
[],
isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' },
);
} catch (error) {
console.error(error);
}

actualServerProcess.stdout?.on('data', (chunk: Buffer) => {
// Send the Server console.log messages to the main browser window
clientWin?.webContents.executeJavaScript(`
console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`);
});

actualServerProcess.stderr?.on('data', (chunk: Buffer) => {
// Send the Server console.error messages out to the main browser window
clientWin?.webContents.executeJavaScript(`
console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`);
});
}

async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) {
const hasRequiredConfig =
ngrokConfig?.authToken && ngrokConfig?.domain && ngrokConfig?.port;

if (!hasRequiredConfig) {
console.error('Cannot expose sync server: missing ngrok settings');
return { error: 'Missing ngrok settings' };
}

try {
const listener = await ngrok.forward({
schemes: ['https'], // change this to https and bind certificate - may need to generate cert and store in user-data
addr: ngrokConfig.port,
host_header: `localhost:${ngrokConfig.port}`,
authtoken: ngrokConfig.authToken,
domain: ngrokConfig.domain,
// crt: fs.readFileSync("crt.pem", "utf8"),
// key: fs.readFileSync("key.pem", "utf8"),
});

console.info(`Exposing actual server on url: ${listener.url()}`);
return { url: listener.url() };
} catch (error) {
console.error('Unable to run ngrok', error);
return { error: `Unable to run ngrok. ${error}` };
}
}

async function createWindow() {
const windowState = await getWindowState();

Expand Down Expand Up @@ -223,6 +303,14 @@ app.on('ready', async () => {
// 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

globalPrefs = await loadGlobalPrefs(); // load global prefs

if (globalPrefs.ngrokConfig.autoStart) {
startSyncServer();
exposeSyncServer(globalPrefs.ngrokConfig);
}

protocol.handle('app', request => {
if (request.method !== 'GET') {
return new Response(null, {
Expand Down Expand Up @@ -373,6 +461,14 @@ ipcMain.handle('open-external-url', (event, url) => {
shell.openExternal(url);
});

ipcMain.handle('start-actual-server', async () => startSyncServer());

ipcMain.handle(
'expose-actual-server',
async (_event, payload: GlobalPrefs['ngrokConfig']) =>
exposeSyncServer(payload),
);

ipcMain.on('message', (_event, msg) => {
if (!serverProcess) {
return;
Expand Down
14 changes: 3 additions & 11 deletions packages/desktop-electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,10 @@
},
"win": {
"target": [
{
"target": "appx",
"arch": [
"ia32",
"x64",
"arm64"
]
},
{
"target": "nsis",
"arch": [
"ia32",
"x64",
"arm64"
"x64"
]
}
],
Expand All @@ -85,6 +75,8 @@
"npmRebuild": false
},
"dependencies": {
"@ngrok/ngrok": "^1.4.1",
"actual-sync": "file:../../../actual-server",
"better-sqlite3": "^9.6.0",
"fs-extra": "^11.2.0",
"node-fetch": "^2.7.0",
Expand Down
12 changes: 12 additions & 0 deletions packages/desktop-electron/preload.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ipcRenderer, contextBridge, IpcRenderer } from 'electron';

import { GlobalPrefs } from 'loot-core/types/prefs';

import {
GetBootstrapDataPayload,
OpenFileDialogPayload,
Expand Down Expand Up @@ -58,6 +60,16 @@ contextBridge.exposeInMainWorld('Actual', {
ipcRenderer.invoke('open-external-url', url);
},

startActualServer: (releaseVersion: string) => {
return ipcRenderer.invoke('start-actual-server', {
releaseVersion,
});
},

exposeActualServer: (settings: GlobalPrefs['ngrokConfig']) => {
return ipcRenderer.invoke('expose-actual-server', settings);
},

onEventFromMain: (type: string, handler: (...args: unknown[]) => void) => {
ipcRenderer.on(type, handler);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function init(opts?: { persist?: boolean }): void;
export type Init = typeof init;

export function getItem(key: string): Promise<string>;
export function getItem<T = string>(key: string): Promise<T>;
export type GetItem = typeof getItem;

export function setItem(key: string, value: unknown): void;
Expand All @@ -10,7 +10,7 @@ export type SetItem = typeof setItem;
export function removeItem(key: string): void;
export type RemoveItem = typeof removeItem;

export function multiGet(keys: string[]): Promise<[string, string][]>;
export function multiGet(keys: string[]): Promise<[string, unknown][]>;
export type MultiGet = typeof multiGet;

export function multiSet(keyValues: [string, unknown][]): void;
Expand Down
6 changes: 6 additions & 0 deletions packages/loot-core/src/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,9 @@ handlers['save-global-prefs'] = async function (prefs) {
prefs.serverSelfSignedCert,
);
}
if ('ngrokConfig' in prefs) {
await asyncStorage.setItem('ngrokConfig', prefs.ngrokConfig);
}
return 'ok';
};

Expand All @@ -1274,6 +1277,7 @@ handlers['load-global-prefs'] = async function () {
[, theme],
[, preferredDarkTheme],
[, serverSelfSignedCert],
[, ngrokConfig],
] = await asyncStorage.multiGet([
'floating-sidebar',
'max-months',
Expand All @@ -1282,6 +1286,7 @@ handlers['load-global-prefs'] = async function () {
'theme',
'preferred-dark-theme',
'server-self-signed-cert',
'ngrokConfig',
]);
return {
floatingSidebar: floatingSidebar === 'true' ? true : false,
Expand All @@ -1301,6 +1306,7 @@ handlers['load-global-prefs'] = async function () {
? preferredDarkTheme
: 'dark',
serverSelfSignedCert: serverSelfSignedCert || undefined,
ngrokConfig: ngrokConfig || undefined,
};
};

Expand Down
7 changes: 7 additions & 0 deletions packages/loot-core/src/types/prefs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,11 @@ export type GlobalPrefs = Partial<{
preferredDarkTheme: DarkTheme;
documentDir: string; // Electron only
serverSelfSignedCert: string; // Electron only
ngrokConfig?: {
// Electron only
autoStart?: boolean;
authToken?: string;
port?: number;
domain?: string;
};
}>;
7 changes: 7 additions & 0 deletions packages/loot-core/typings/window.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { GlobalPrefs } from 'loot-core/types/prefs';

export {};

declare global {
Expand All @@ -6,6 +8,11 @@ declare global {
IS_FAKE_WEB: boolean;
ACTUAL_VERSION: string;
openURLInBrowser: (url: string) => void;
downloadActualServer: (releaseVersion: string) => Promise<void>;
startActualServer: (releaseVersion: string) => Promise<void>;
exposeActualServer: (
settings: GlobalPrefs['ngrokConfig'],
) => Promise<{ url?: string; error?: string } | undefined>;
saveFile: (
contents: string | Buffer,
filename: string,
Expand Down
Loading
Loading