:electron: Prep for configure server page (#4847)

* prep work for configure server page

* release notes

* better log message
This commit is contained in:
Michael Clark
2025-04-19 18:34:13 +01:00
committed by GitHub
parent 2f1ccad25a
commit e67c064d1f
11 changed files with 106 additions and 40 deletions

View File

@@ -86,6 +86,12 @@ global.Actual = {
});
},
startSyncServer: () => {},
stopSyncServer: () => {},
isSyncServerRunning: () => false,
startOAuthServer: () => {
return '';
},

View File

@@ -65,7 +65,7 @@ if (isPlaywrightTest) {
// be closed automatically when the JavaScript object is garbage collected.
let clientWin: BrowserWindow | null;
let serverProcess: UtilityProcess | null;
let actualServerProcess: UtilityProcess | null;
let syncServerProcess: UtilityProcess | null;
let oAuthServer: ReturnType<typeof createServer> | null;
@@ -264,19 +264,19 @@ async function startSyncServer() {
let syncServerStarted = false;
const syncServerPromise = new Promise<void>(resolve => {
actualServerProcess = utilityProcess.fork(serverPath, [], forkOptions);
syncServerProcess = utilityProcess.fork(serverPath, [], forkOptions);
actualServerProcess.stdout?.on('data', (chunk: Buffer) => {
syncServerProcess.stdout?.on('data', (chunk: Buffer) => {
// Send the Server console.log messages to the main browser window
logMessage('info', `Sync-Server: ${chunk.toString('utf8')}`);
});
actualServerProcess.stderr?.on('data', (chunk: Buffer) => {
syncServerProcess.stderr?.on('data', (chunk: Buffer) => {
// Send the Server console.error messages out to the main browser window
logMessage('error', `Sync-Server: ${chunk.toString('utf8')}`);
});
actualServerProcess.on('message', msg => {
syncServerProcess.on('message', msg => {
switch (msg.type) {
case 'server-started':
logMessage('info', 'Sync-Server: Actual Sync Server has started!');
@@ -310,6 +310,12 @@ async function startSyncServer() {
}
}
async function stopSyncServer() {
syncServerProcess?.kill();
syncServerProcess = null;
logMessage('info', 'Sync-Server: Stopped');
}
async function createWindow() {
const windowState = await getWindowState();
@@ -548,6 +554,14 @@ ipcMain.on('get-bootstrap-data', event => {
event.returnValue = payload;
});
ipcMain.handle('start-sync-server', async () => startSyncServer());
ipcMain.handle('stop-sync-server', async () => stopSyncServer());
ipcMain.handle('is-sync-server-running', async () =>
syncServerProcess ? true : false,
);
ipcMain.handle('start-oauth-server', async () => {
const { url, server: newServer } = await createOAuthServer();
oAuthServer = newServer;

View File

@@ -29,6 +29,12 @@ contextBridge.exposeInMainWorld('Actual', {
});
},
startSyncServer: () => ipcRenderer.invoke('start-sync-server'),
stopSyncServer: () => ipcRenderer.invoke('stop-sync-server'),
isSyncServerRunning: () => ipcRenderer.invoke('is-sync-server-running'),
startOAuthServer: () => ipcRenderer.invoke('start-oauth-server'),
relaunch: () => {

View File

@@ -20,14 +20,19 @@ export const removeItem: T.RemoveItem = async function (key) {
export async function multiGet<K extends readonly (keyof GlobalPrefsJson)[]>(
keys: K,
) {
return new Promise(function (resolve) {
return resolve(
keys.map(function (key) {
return [key, store[key]];
}) as { [P in keyof K]: [K[P], GlobalPrefsJson[K[P]]] },
);
});
): Promise<{ [P in K[number]]: GlobalPrefsJson[P] }> {
const results = keys.map(key => [key, store[key]]) as {
[P in keyof K]: [K[P], GlobalPrefsJson[K[P]]];
};
// Convert the array of tuples to an object with properly typed properties
return results.reduce(
(acc, [key, value]) => {
acc[key] = value;
return acc;
},
{} as { [P in K[number]]: GlobalPrefsJson[P] },
);
}
export const multiSet: T.MultiSet = async function (keyValues) {

View File

@@ -21,7 +21,8 @@ export type RemoveItem = typeof removeItem;
export function multiGet<K extends readonly (keyof GlobalPrefsJson)[]>(
keys: K,
): Promise<{ [P in keyof K]: [K[P], GlobalPrefsJson[K[P]]] }>;
): Promise<{ [P in K[number]]: GlobalPrefsJson[P] }>;
export type MultiGet = typeof multiGet;
export function multiSet<K extends keyof GlobalPrefsJson>(

View File

@@ -58,10 +58,19 @@ export const removeItem: T.RemoveItem = function (key) {
export async function multiGet<K extends readonly (keyof GlobalPrefsJson)[]>(
keys: K,
) {
return keys.map(key => [key, store[key]]) as {
): Promise<{ [P in K[number]]: GlobalPrefsJson[P] }> {
const results = keys.map(key => [key, store[key]]) as {
[P in keyof K]: [K[P], GlobalPrefsJson[K[P]]];
};
// Convert the array of tuples to an object with properly typed properties
return results.reduce(
(acc, [key, value]) => {
acc[key] = value;
return acc;
},
{} as { [P in K[number]]: GlobalPrefsJson[P] },
);
}
export const multiSet: T.MultiSet = function (keyValues) {

View File

@@ -50,25 +50,37 @@ export const removeItem: T.RemoveItem = async function (key) {
export async function multiGet<K extends readonly (keyof GlobalPrefsJson)[]>(
keys: K,
) {
): Promise<{ [P in K[number]]: GlobalPrefsJson[P] }> {
const db = await getDatabase();
const transaction = db.transaction(['asyncStorage'], 'readonly');
const objectStore = transaction.objectStore('asyncStorage');
const promise = Promise.all(
const results = await Promise.all(
keys.map(key => {
return new Promise<[string, string]>((resolve, reject) => {
const req = objectStore.get(key);
req.onerror = e => reject(e);
// @ts-expect-error fix me
req.onsuccess = e => resolve([key, e.target.result]);
});
return new Promise<[K[number], GlobalPrefsJson[K[number]]]>(
(resolve, reject) => {
const req = objectStore.get(key);
req.onerror = e => reject(e);
req.onsuccess = e => {
const target = e.target as IDBRequest<GlobalPrefsJson[K[number]]>;
resolve([key, target.result]);
};
},
);
}),
);
transaction.commit();
return promise;
// Convert the array of tuples to an object with properly typed properties
return results.reduce(
(acc, [key, value]) => {
acc[key] = value;
return acc;
},
{} as { [P in K[number]]: GlobalPrefsJson[P] },
);
}
export const multiSet: T.MultiSet = async function (keyValues) {

View File

@@ -883,10 +883,8 @@ async function accountsBankSync({
}: {
ids: Array<AccountEntity['id']>;
}): Promise<SyncResponseWithErrors> {
const [[, userId], [, userKey]] = await asyncStorage.multiGet([
'user-id',
'user-key',
]);
const { 'user-id': userId, 'user-key': userKey } =
await asyncStorage.multiGet(['user-id', 'user-key']);
const accounts = await db.runQuery<
db.DbAccount & { bankId: db.DbBank['bank_id'] }

View File

@@ -99,21 +99,25 @@ async function saveGlobalPrefs(prefs: GlobalPrefs) {
prefs.serverSelfSignedCert,
);
}
if (prefs.syncServerConfig !== undefined) {
await asyncStorage.setItem('syncServerConfig', prefs.syncServerConfig);
}
return 'ok';
}
async function loadGlobalPrefs(): Promise<GlobalPrefs> {
const [
[, floatingSidebar],
[, categoryExpandedState],
[, maxMonths],
[, documentDir],
[, encryptKey],
[, language],
[, theme],
[, preferredDarkTheme],
[, serverSelfSignedCert],
] = await asyncStorage.multiGet([
const {
'floating-sidebar': floatingSidebar,
'category-expanded-state': categoryExpandedState,
'max-months': maxMonths,
'document-dir': documentDir,
'encrypt-key': encryptKey,
language,
theme,
'preferred-dark-theme': preferredDarkTheme,
'server-self-signed-cert': serverSelfSignedCert,
syncServerConfig,
} = await asyncStorage.multiGet([
'floating-sidebar',
'category-expanded-state',
'max-months',
@@ -123,6 +127,7 @@ async function loadGlobalPrefs(): Promise<GlobalPrefs> {
'theme',
'preferred-dark-theme',
'server-self-signed-cert',
'syncServerConfig',
] as const);
return {
floatingSidebar: floatingSidebar === 'true',
@@ -144,6 +149,7 @@ async function loadGlobalPrefs(): Promise<GlobalPrefs> {
? preferredDarkTheme
: 'dark',
serverSelfSignedCert: serverSelfSignedCert || undefined,
syncServerConfig: syncServerConfig || undefined,
};
}

View File

@@ -29,6 +29,9 @@ type Actual = {
) => void;
isUpdateReadyForDownload: () => boolean;
waitForUpdateReadyForDownload: () => Promise<void>;
startSyncServer: () => Promise<void>;
stopSyncServer: () => Promise<void>;
isSyncServerRunning: () => Promise<boolean>;
startOAuthServer: () => Promise<string>;
};

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [MikesGlitch]
---
Allowing the UI to call internal sync server commands when running in the Desktop app