From 20e2ef27b2e72858ca673131de2ca0197d862040 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 10 Oct 2024 20:04:27 +0100 Subject: [PATCH 01/33] start to this epic idea --- .../src/browser-preload.browser.js | 1 + packages/desktop-electron/index.ts | 28 +++++++++++++++++++ packages/desktop-electron/package.json | 1 + packages/desktop-electron/preload.ts | 6 ++++ packages/loot-core/typings/window.d.ts | 1 + yarn.lock | 1 + 6 files changed, 38 insertions(+) diff --git a/packages/desktop-client/src/browser-preload.browser.js b/packages/desktop-client/src/browser-preload.browser.js index a65e4b09a24..d433fcc50dd 100644 --- a/packages/desktop-client/src/browser-preload.browser.js +++ b/packages/desktop-client/src/browser-preload.browser.js @@ -139,6 +139,7 @@ global.Actual = { openURLInBrowser: url => { window.open(url, '_blank'); }, + downloadActualServer: () => {}, onEventFromMain: () => {}, applyAppUpdate: () => {}, updateAppMenu: () => {}, diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 92649a96c1f..b64aaaa5c24 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -26,6 +26,7 @@ import { } from './window-state'; import './security'; +import AdmZip from 'adm-zip'; const isDev = !app.isPackaged; // dev mode if not packaged @@ -373,6 +374,33 @@ ipcMain.handle('open-external-url', (event, url) => { shell.openExternal(url); }); +ipcMain.handle( + 'download-actual-server', + async (_event, payload: { releaseVersion: string }) => { + console.info({ payload }); + const downloadUrl = `https://github.com/MikesGlitch/actual-server/releases/download/${payload.releaseVersion}/${payload.releaseVersion}-server-sync-dist.zip`; + + try { + const res = await fetch(downloadUrl); + const arrBuffer = await res.arrayBuffer(); + const zipped = new AdmZip(Buffer.from(arrBuffer)); + console.info( + 'actual-server will be installed here:', + process.env.ACTUAL_DATA_DIR, + ); + zipped.extractAllTo( + process.env.ACTUAL_DATA_DIR + '/actual-server-releases', + true, + false, + ); + return { error: undefined }; + } catch (error) { + console.error('Error retrieving actual-server:', error); + return { error }; + } + }, +); + ipcMain.on('message', (_event, msg) => { if (!serverProcess) { return; diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 2a353b11360..001c26f1b79 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -85,6 +85,7 @@ "npmRebuild": false }, "dependencies": { + "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", "node-fetch": "^2.7.0", diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index c6f8327ebf5..770106892cc 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -58,6 +58,12 @@ contextBridge.exposeInMainWorld('Actual', { ipcRenderer.invoke('open-external-url', url); }, + downloadActualServer: async (releaseVersion: string) => { + await ipcRenderer.invoke('download-actual-server', { + releaseVersion, + }); + }, + onEventFromMain: (type: string, handler: (...args: unknown[]) => void) => { ipcRenderer.on(type, handler); }, diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 1a56a14fb20..b8f2f0a06f5 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -6,6 +6,7 @@ declare global { IS_FAKE_WEB: boolean; ACTUAL_VERSION: string; openURLInBrowser: (url: string) => void; + downloadActualServer: (releaseVersion: string) => Promise; saveFile: ( contents: string | Buffer, filename: string, diff --git a/yarn.lock b/yarn.lock index 29ce52e201a..c79af54cdfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8432,6 +8432,7 @@ __metadata: "@electron/rebuild": "npm:3.6.0" "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" + adm-zip: "npm:^0.5.10" better-sqlite3: "npm:^9.6.0" copyfiles: "npm:^2.4.1" cross-env: "npm:^7.0.3" From 361200e26c3d76fa30ed436c6ba6da17da9b60da Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 10 Oct 2024 20:46:57 +0100 Subject: [PATCH 02/33] bits --- packages/desktop-electron/index.ts | 3 +-- packages/desktop-electron/preload.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index b64aaaa5c24..485a1bd4285 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -393,10 +393,9 @@ ipcMain.handle( true, false, ); - return { error: undefined }; } catch (error) { console.error('Error retrieving actual-server:', error); - return { error }; + throw error; } }, ); diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index 770106892cc..60da50b7b10 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -58,8 +58,8 @@ contextBridge.exposeInMainWorld('Actual', { ipcRenderer.invoke('open-external-url', url); }, - downloadActualServer: async (releaseVersion: string) => { - await ipcRenderer.invoke('download-actual-server', { + downloadActualServer: (releaseVersion: string) => { + return ipcRenderer.invoke('download-actual-server', { releaseVersion, }); }, From c718b2c442b34f5fa7ac4e2c0fcac8184c75b787 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 10 Oct 2024 20:48:33 +0100 Subject: [PATCH 03/33] add a note --- packages/desktop-electron/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 485a1bd4285..6944ec5270a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -374,6 +374,7 @@ ipcMain.handle('open-external-url', (event, url) => { shell.openExternal(url); }); +// NOTE: We could just bundle it in the package, but it would be a large download - consider it though... ipcMain.handle( 'download-actual-server', async (_event, payload: { releaseVersion: string }) => { From e32fb8b9589aa85ee83731847cc192b0fc016bdb Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Fri, 11 Oct 2024 21:11:32 +0100 Subject: [PATCH 04/33] server starting button erroring on imports due to env differences --- .../src/browser-preload.browser.js | 1 + .../src/components/manager/BudgetList.tsx | 14 ++++++++++ packages/desktop-electron/actualServer.ts | 19 +++++++++++++ packages/desktop-electron/index.ts | 28 +++++++++++++++++++ packages/desktop-electron/preload.ts | 6 ++++ packages/loot-core/typings/window.d.ts | 1 + 6 files changed, 69 insertions(+) create mode 100644 packages/desktop-electron/actualServer.ts diff --git a/packages/desktop-client/src/browser-preload.browser.js b/packages/desktop-client/src/browser-preload.browser.js index d433fcc50dd..43a3037cc92 100644 --- a/packages/desktop-client/src/browser-preload.browser.js +++ b/packages/desktop-client/src/browser-preload.browser.js @@ -140,6 +140,7 @@ global.Actual = { window.open(url, '_blank'); }, downloadActualServer: () => {}, + startActualServer: () => {}, onEventFromMain: () => {}, applyAppUpdate: () => {}, updateAppMenu: () => {}, diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 1cd0f5e3c96..2afadfe3c7f 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -441,6 +441,10 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { } }; + const startActualServer = async () => { + await globalThis.Actual.startActualServer('v24.10.1'); + }; + return ( Create test file )} + )} diff --git a/packages/desktop-electron/actualServer.ts b/packages/desktop-electron/actualServer.ts new file mode 100644 index 00000000000..feda2931d2a --- /dev/null +++ b/packages/desktop-electron/actualServer.ts @@ -0,0 +1,19 @@ +// Sync server (actual-server) + +const _node = process.argv[0]; +const _script = process.argv[1]; +const _subProcess = process.argv[2]; +const actualServerDir = process.argv[3]; + +const lazyLoadActualServer = async () => { + try { + console.info('Starting actual-server...'); + await import(`${actualServerDir}/app.js`); + } catch (error) { + console.error('Failed to start actual-server:', error); + throw new Error(`Failed to start actual-server: ${error}`); + } +}; + +// Start actual-server +lazyLoadActualServer(); diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 6944ec5270a..c0949a6b632 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -52,6 +52,7 @@ 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; if (isDev) { process.traceProcessWarnings = true; @@ -401,6 +402,33 @@ ipcMain.handle( }, ); +ipcMain.handle( + 'start-actual-server', + async (_event, payload: { releaseVersion: string }) => { + const serverReleaseDir = path.resolve( + `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, + ); + + const actualServerProcess = utilityProcess.fork( + __dirname + '/actualServer.js', + ['--subprocess', serverReleaseDir], + isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + ); + + 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'))})`); + }); + }, +); + ipcMain.on('message', (_event, msg) => { if (!serverProcess) { return; diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index 60da50b7b10..2c65ddff64e 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -64,6 +64,12 @@ contextBridge.exposeInMainWorld('Actual', { }); }, + startActualServer: (releaseVersion: string) => { + return ipcRenderer.invoke('start-actual-server', { + releaseVersion, + }); + }, + onEventFromMain: (type: string, handler: (...args: unknown[]) => void) => { ipcRenderer.on(type, handler); }, diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index b8f2f0a06f5..805db112ba6 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -7,6 +7,7 @@ declare global { ACTUAL_VERSION: string; openURLInBrowser: (url: string) => void; downloadActualServer: (releaseVersion: string) => Promise; + startActualServer: (releaseVersion: string) => Promise; saveFile: ( contents: string | Buffer, filename: string, From 1802818f2f340e2fd0cf2136e72c8371ae17d9fc Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Fri, 11 Oct 2024 21:31:39 +0100 Subject: [PATCH 05/33] working now --- packages/desktop-electron/actualServer.ts | 19 ------------------- packages/desktop-electron/index.ts | 2 +- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 packages/desktop-electron/actualServer.ts diff --git a/packages/desktop-electron/actualServer.ts b/packages/desktop-electron/actualServer.ts deleted file mode 100644 index feda2931d2a..00000000000 --- a/packages/desktop-electron/actualServer.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Sync server (actual-server) - -const _node = process.argv[0]; -const _script = process.argv[1]; -const _subProcess = process.argv[2]; -const actualServerDir = process.argv[3]; - -const lazyLoadActualServer = async () => { - try { - console.info('Starting actual-server...'); - await import(`${actualServerDir}/app.js`); - } catch (error) { - console.error('Failed to start actual-server:', error); - throw new Error(`Failed to start actual-server: ${error}`); - } -}; - -// Start actual-server -lazyLoadActualServer(); diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index c0949a6b632..fa8888c26da 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -410,7 +410,7 @@ ipcMain.handle( ); const actualServerProcess = utilityProcess.fork( - __dirname + '/actualServer.js', + serverReleaseDir + '/app.js', ['--subprocess', serverReleaseDir], isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, ); From 194f50d889133bb2ceaf19e77f4d0985f975d15c Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sat, 12 Oct 2024 11:25:38 +0100 Subject: [PATCH 06/33] using node_modules and allowing electron-builder to bundle it --- packages/desktop-electron/index.ts | 29 +- packages/desktop-electron/package.json | 13 +- yarn.lock | 1000 +++++++++++++++++++++++- 3 files changed, 996 insertions(+), 46 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index fa8888c26da..6b0f81403b1 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -409,22 +409,39 @@ ipcMain.handle( `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, ); - const actualServerProcess = utilityProcess.fork( - serverReleaseDir + '/app.js', - ['--subprocess', serverReleaseDir], - isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + // actualServerProcess = utilityProcess.fork( + // serverReleaseDir + '/app.js', // if bundling it ourselves and manually including it + // ['--subprocess'], + // isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, + // ); + + 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'))})`); + 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'))})`); + console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); }, ); diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 001c26f1b79..6eaeb555318 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -56,20 +56,10 @@ }, "win": { "target": [ - { - "target": "appx", - "arch": [ - "ia32", - "x64", - "arm64" - ] - }, { "target": "nsis", "arch": [ - "ia32", - "x64", - "arm64" + "x64" ] } ], @@ -85,6 +75,7 @@ "npmRebuild": false }, "dependencies": { + "actual-sync": "file:../../../actual-server", "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", diff --git a/yarn.lock b/yarn.lock index c79af54cdfe..439310d4fe2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,7 +38,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/crdt@npm:*, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": +"@actual-app/crdt@npm:*, @actual-app/crdt@npm:2.1.0, @actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": version: 0.0.0-use.local resolution: "@actual-app/crdt@workspace:packages/crdt" dependencies: @@ -55,6 +55,13 @@ __metadata: languageName: unknown linkType: soft +"@actual-app/web@npm:24.10.0": + version: 24.10.0 + resolution: "@actual-app/web@npm:24.10.0" + checksum: 10/e713d98f69d7a8598f863c0fe559c2a87d56f0adc31e3bea62338bf9f9e8cfefc1fc41597e8ef0e3cdc1c51a611e330d023c9cde92e1d7b2b0a3f90e8a690254 + languageName: node + linkType: hard + "@actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" @@ -1507,6 +1514,13 @@ __metadata: languageName: node linkType: hard +"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10/66d00284a3a9a21e5e853b256942e17edbb295f4bd7b9aa7ef06bbb603568d5173eb41b0f64c1e51748bc29d382a23a67d99956e57e7431c64e47e74324182d9 + languageName: node + linkType: hard + "@cspotcode/source-map-support@npm:^0.8.0": version: 0.8.1 resolution: "@cspotcode/source-map-support@npm:0.8.1" @@ -1516,6 +1530,17 @@ __metadata: languageName: node linkType: hard +"@dabh/diagnostics@npm:^2.0.2": + version: 2.0.3 + resolution: "@dabh/diagnostics@npm:2.0.3" + dependencies: + colorspace: "npm:1.1.x" + enabled: "npm:2.0.x" + kuler: "npm:^2.0.0" + checksum: 10/14e449a7f42f063f959b472f6ce02d16457a756e852a1910aaa831b63fc21d86f6c32b2a1aa98a4835b856548c926643b51062d241fb6e9b2b7117996053e6b9 + languageName: node + linkType: hard + "@develar/schema-utils@npm:~2.6.5": version: 2.6.5 resolution: "@develar/schema-utils@npm:2.6.5" @@ -2428,6 +2453,25 @@ __metadata: languageName: node linkType: hard +"@mapbox/node-pre-gyp@npm:^1.0.11": + version: 1.0.11 + resolution: "@mapbox/node-pre-gyp@npm:1.0.11" + dependencies: + detect-libc: "npm:^2.0.0" + https-proxy-agent: "npm:^5.0.0" + make-dir: "npm:^3.1.0" + node-fetch: "npm:^2.6.7" + nopt: "npm:^5.0.0" + npmlog: "npm:^5.0.1" + rimraf: "npm:^3.0.2" + semver: "npm:^7.3.5" + tar: "npm:^6.1.11" + bin: + node-pre-gyp: bin/node-pre-gyp + checksum: 10/59529a2444e44fddb63057152452b00705aa58059079191126c79ac1388ae4565625afa84ed4dd1bf017d1111ab6e47907f7c5192e06d83c9496f2f3e708680a + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -5449,6 +5493,13 @@ __metadata: languageName: node linkType: hard +"@types/triple-beam@npm:^1.3.2": + version: 1.3.5 + resolution: "@types/triple-beam@npm:1.3.5" + checksum: 10/519b6a1b30d4571965c9706ad5400a200b94e4050feca3e7856e3ea7ac00ec9903e32e9a10e2762d0f7e472d5d03e5f4b29c16c0bd8c1f77c8876c683b2231f1 + languageName: node + linkType: hard + "@types/trusted-types@npm:^2.0.2": version: 2.0.7 resolution: "@types/trusted-types@npm:2.0.7" @@ -5967,13 +6018,22 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^1.0.0": +"abbrev@npm:1, abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" checksum: 10/2d882941183c66aa665118bafdab82b7a177e9add5eb2776c33e960a4f3c89cff88a1b38aba13a456de01d0dd9d66a8bea7c903268b21ea91dd1097e1e2e8243 languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: "npm:^5.0.0" + checksum: 10/ed84af329f1828327798229578b4fe03a4dd2596ba304083ebd2252666bdc1d7647d66d0b18704477e1f8aa315f055944aa6e859afebd341f12d0a53c37b4b40 + languageName: node + linkType: hard + "absurd-sql@npm:0.0.54": version: 0.0.54 resolution: "absurd-sql@npm:0.0.54" @@ -5983,6 +6043,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10/67eaaa90e2917c58418e7a9b89392002d2b1ccd69bcca4799135d0c632f3b082f23f4ae4ddeedbced5aa59bcc7bdf4699c69ebed4593696c922462b7bc5744d6 + languageName: node + linkType: hard + "acorn-globals@npm:^6.0.0": version: 6.0.0 resolution: "acorn-globals@npm:6.0.0" @@ -6043,6 +6113,32 @@ __metadata: languageName: node linkType: hard +"actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": + version: 24.10.0 + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=bcb84c&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + dependencies: + "@actual-app/crdt": "npm:2.1.0" + "@actual-app/web": "npm:24.10.0" + bcrypt: "npm:^5.1.1" + better-sqlite3: "npm:^9.6.0" + body-parser: "npm:^1.20.3" + cors: "npm:^2.8.5" + date-fns: "npm:^2.30.0" + debug: "npm:^4.3.4" + express: "npm:4.20.0" + express-actuator: "npm:1.8.4" + express-rate-limit: "npm:^6.7.0" + express-response-size: "npm:^0.0.3" + express-winston: "npm:^4.2.0" + jws: "npm:^4.0.0" + migrate: "npm:^2.0.1" + nordigen-node: "npm:^1.4.0" + uuid: "npm:^9.0.0" + winston: "npm:^3.14.2" + checksum: 10/eb84130ce8e62e8292658b12f48b5cd36d4647a37c22277842781dc1425f98475f47b24cb5c77c0c1695ee33b3ba05e842bacb7fb0ea511a8ee1f5f5a83760ac + languageName: node + linkType: hard + "actual@workspace:.": version: 0.0.0-use.local resolution: "actual@workspace:." @@ -6290,6 +6386,16 @@ __metadata: languageName: node linkType: hard +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: "npm:^1.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10/ea6f47d14fc33ae9cbea3e686eeca021d9d7b9db83a306010dd04ad5f2c8b7675291b127d3fcbfcbd8fec26e47b3324ad5b469a6cc3733a582f2fe4e12fc6756 + languageName: node + linkType: hard + "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -6342,6 +6448,13 @@ __metadata: languageName: node linkType: hard +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 10/e13c9d247241be82f8b4ec71d035ed7204baa82fae820d4db6948d30d3c4a9f2b3905eb2eec2b937d4aa3565200bd3a1c500480114cff649fa748747d2a50feb + languageName: node + linkType: hard + "array-includes@npm:^3.1.6, array-includes@npm:^3.1.7, array-includes@npm:^3.1.8": version: 3.1.8 resolution: "array-includes@npm:3.1.8" @@ -6545,6 +6658,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.2.1": + version: 1.7.7 + resolution: "axios@npm:1.7.7" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10/7f875ea13b9298cd7b40fd09985209f7a38d38321f1118c701520939de2f113c4ba137832fe8e3f811f99a38e12c8225481011023209a77b0c0641270e20cde1 + languageName: node + linkType: hard + "axobject-query@npm:~3.1.1": version: 3.1.1 resolution: "axobject-query@npm:3.1.1" @@ -6709,6 +6833,16 @@ __metadata: languageName: node linkType: hard +"bcrypt@npm:^5.1.1": + version: 5.1.1 + resolution: "bcrypt@npm:5.1.1" + dependencies: + "@mapbox/node-pre-gyp": "npm:^1.0.11" + node-addon-api: "npm:^5.0.0" + checksum: 10/be6af3a93d90a0071c3b4412e8b82e2f319e26cb4e6cb14a1790cfe7c164792fa8add3ac9f30278a017d7d332ee8852601ce81a69737e9bfb9f10c878dd3d0dd + languageName: node + linkType: hard + "better-sqlite3@npm:^9.6.0": version: 9.6.0 resolution: "better-sqlite3@npm:9.6.0" @@ -6797,6 +6931,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:1.20.3, body-parser@npm:^1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.13.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 10/8723e3d7a672eb50854327453bed85ac48d045f4958e81e7d470c56bf111f835b97e5b73ae9f6393d0011cc9e252771f46fd281bbabc57d33d3986edf1e6aeca + languageName: node + linkType: hard + "boolbase@npm:^1.0.0": version: 1.0.0 resolution: "boolbase@npm:1.0.0" @@ -6949,6 +7103,13 @@ __metadata: languageName: node linkType: hard +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10/80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab + languageName: node + linkType: hard + "buffer-equal@npm:^1.0.0": version: 1.0.1 resolution: "buffer-equal@npm:1.0.1" @@ -7031,6 +7192,13 @@ __metadata: languageName: node linkType: hard +"bytes@npm:3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 + languageName: node + linkType: hard + "cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" @@ -7521,7 +7689,7 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:^1.9.0": +"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": version: 1.9.3 resolution: "color-convert@npm:1.9.3" dependencies: @@ -7546,14 +7714,24 @@ __metadata: languageName: node linkType: hard -"color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard -"color-support@npm:^1.1.3": +"color-string@npm:^1.6.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10/72aa0b81ee71b3f4fb1ac9cd839cdbd7a011a7d318ef58e6cb13b3708dca75c7e45029697260488709f1b1c7ac4e35489a87e528156c1e365917d1c4ccb9b9cd + languageName: node + linkType: hard + +"color-support@npm:^1.1.2, color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" bin: @@ -7562,6 +7740,16 @@ __metadata: languageName: node linkType: hard +"color@npm:^3.1.3": + version: 3.2.1 + resolution: "color@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.3" + color-string: "npm:^1.6.0" + checksum: 10/bf70438e0192f4f62f4bfbb303e7231289e8cc0d15ff6b6cbdb722d51f680049f38d4fdfc057a99cb641895cf5e350478c61d98586400b060043afc44285e7ae + languageName: node + linkType: hard + "colorette@npm:^2.0.14, colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" @@ -7576,6 +7764,16 @@ __metadata: languageName: node linkType: hard +"colorspace@npm:1.1.x": + version: 1.1.4 + resolution: "colorspace@npm:1.1.4" + dependencies: + color: "npm:^3.1.3" + text-hex: "npm:1.0.x" + checksum: 10/bb3934ef3c417e961e6d03d7ca60ea6e175947029bfadfcdb65109b01881a1c0ecf9c2b0b59abcd0ee4a0d7c1eae93beed01b0e65848936472270a0b341ebce8 + languageName: node + linkType: hard + "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -7599,7 +7797,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.20.0, commander@npm:^2.8.1": +"commander@npm:^2.20.0, commander@npm:^2.20.3, commander@npm:^2.8.1": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 10/90c5b6898610cd075984c58c4f88418a4fb44af08c1b1415e9854c03171bec31b336b7f3e4cefe33de994b3f12b03c5e2d638da4316df83593b9e82554e7e95b @@ -7696,14 +7894,14 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.1.0": +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" checksum: 10/27b5fa302bc8e9ae9e98c03c66d76ca289ad0c61ce2fe20ab288d288bee875d217512d2edb2363fc83165e88f1c405180cf3f5413a46e51b4fe1a004840c6cdb languageName: node linkType: hard -"content-disposition@npm:^0.5.2": +"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.2": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -7712,6 +7910,13 @@ __metadata: languageName: node linkType: hard +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 + languageName: node + linkType: hard + "convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.6.0": version: 1.9.0 resolution: "convert-source-map@npm:1.9.0" @@ -7726,6 +7931,20 @@ __metadata: languageName: node linkType: hard +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: 10/f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a + languageName: node + linkType: hard + +"cookie@npm:0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: 10/c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 + languageName: node + linkType: hard + "copyfiles@npm:^2.4.1": version: 2.4.1 resolution: "copyfiles@npm:2.4.1" @@ -7781,6 +8000,16 @@ __metadata: languageName: node linkType: hard +"cors@npm:^2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10/66e88e08edee7cbce9d92b4d28a2028c88772a4c73e02f143ed8ca76789f9b59444eed6b1c167139e76fa662998c151322720093ba229f9941365ada5a6fc2c6 + languageName: node + linkType: hard + "cosmiconfig@npm:^8.1.3": version: 8.2.0 resolution: "cosmiconfig@npm:8.2.0" @@ -8140,6 +8369,20 @@ __metadata: languageName: node linkType: hard +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: 10/5c149c91bf9ce2142c89f84eee4c585f0cb1f6faf2536b1af89873f862666a28529d1ccafc44750aa01384da2197c4f76f4e149a3cc0c1cb2c46f5cc45f2bcb5 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.3": + version: 1.11.13 + resolution: "dayjs@npm:1.11.13" + checksum: 10/7374d63ab179b8d909a95e74790def25c8986e329ae989840bacb8b1888be116d20e1c4eee75a69ea0dfbae13172efc50ef85619d304ee7ca3c01d5878b704f5 + languageName: node + linkType: hard + "debounce@npm:^1.2.1": version: 1.2.1 resolution: "debounce@npm:1.2.1" @@ -8147,6 +8390,15 @@ __metadata: languageName: node linkType: hard +"debug@npm:2.6.9, debug@npm:^2.2.0": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10/e07005f2b40e04f1bd14a3dd20520e9c4f25f60224cb006ce9d6781732c917964e9ec029fc7f1a151083cd929025ad5133814d4dc624a9aaf020effe4914ed14 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.6": version: 4.3.6 resolution: "debug@npm:4.3.6" @@ -8159,15 +8411,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:^2.2.0": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: "npm:2.0.0" - checksum: 10/e07005f2b40e04f1bd14a3dd20520e9c4f25f60224cb006ce9d6781732c917964e9ec029fc7f1a151083cd929025ad5133814d4dc624a9aaf020effe4914ed14 - languageName: node - linkType: hard - "debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -8410,7 +8653,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:^2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca @@ -8432,6 +8675,7 @@ __metadata: "@electron/rebuild": "npm:3.6.0" "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" + actual-sync: "file:../../../actual-server" adm-zip: "npm:^0.5.10" better-sqlite3: "npm:^9.6.0" copyfiles: "npm:^2.4.1" @@ -8445,6 +8689,13 @@ __metadata: languageName: unknown linkType: soft +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 + languageName: node + linkType: hard + "detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1": version: 2.0.2 resolution: "detect-libc@npm:2.0.2" @@ -8667,6 +8918,20 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv@npm:10.0.0" + checksum: 10/55f701ae213e3afe3f4232fae5edfb6e0c49f061a363ff9f1c5a0c2bf3fb990a6e49aeada11b2a116efb5fdc3bc3f1ef55ab330be43033410b267f7c0809a9dc + languageName: node + linkType: hard + +"dotenv@npm:^16.0.0": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10/55a3134601115194ae0f924e54473459ed0d9fc340ae610b676e248cca45aa7c680d86365318ea964e6da4e2ea80c4514c1adab5adb43d6867fb57ff068f95c8 + languageName: node + linkType: hard + "dotenv@npm:^9.0.2": version: 9.0.2 resolution: "dotenv@npm:9.0.2" @@ -8729,6 +8994,22 @@ __metadata: languageName: node linkType: hard +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10/878e1aab8a42773320bc04c6de420bee21aebd71810e40b1799880a8a1c4594bcd6adc3d4213a0fb8147d4c3f529d8f9a618d7f59ad5a9a41b142058aceda23f + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10/1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f + languageName: node + linkType: hard + "ejs@npm:^3.1.6, ejs@npm:^3.1.8": version: 3.1.9 resolution: "ejs@npm:3.1.9" @@ -8832,6 +9113,27 @@ __metadata: languageName: node linkType: hard +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 10/9d256d89f4e8a46ff988c6a79b22fa814b4ffd82826c4fdacd9b42e9b9465709d3b748866d0ab4d442dfc6002d81de7f7b384146ccd1681f6a7f868d2acca063 + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10/e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c + languageName: node + linkType: hard + +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + "encoding@npm:^0.1.11, encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -9170,6 +9472,13 @@ __metadata: languageName: node linkType: hard +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10/6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 + languageName: node + linkType: hard + "escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -9632,6 +9941,20 @@ __metadata: languageName: node linkType: hard +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10/571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff + languageName: node + linkType: hard + +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 10/49ff46c3a7facbad3decb31f597063e761785d7fdb3920d4989d7b08c97a61c2f51183e2f3a03130c9088df88d4b489b1b79ab632219901f184f85158508f4c8 + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.1": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -9646,7 +9969,7 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.2.0": +"events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: 10/a3d47e285e28d324d7180f1e493961a2bbb4cad6412090e4dec114f4db1f5b560c7696ee8e758f55e23913ede856e3689cd3aa9ae13c56b5d8314cd3b3ddd1be @@ -9772,6 +10095,85 @@ __metadata: languageName: node linkType: hard +"express-actuator@npm:1.8.4": + version: 1.8.4 + resolution: "express-actuator@npm:1.8.4" + dependencies: + dayjs: "npm:^1.11.3" + properties-reader: "npm:^2.2.0" + checksum: 10/5f55418bfd660e8781a777bf48c7eeda2d0a38fa11b2f4670555123380f522eed5bb884df297fb420904663bd2057834227c0eea68d6f852432ac3bc77842c09 + languageName: node + linkType: hard + +"express-rate-limit@npm:^6.7.0": + version: 6.11.2 + resolution: "express-rate-limit@npm:6.11.2" + peerDependencies: + express: ^4 || ^5 + checksum: 10/9b482cf91e030edcb88292831b2515208ddb9ec92330c54fb487c700fe8ac5000c7f5d2623ae4913b5a7fcce8e9ef65eb017e28edc96ace0ed111c16b996ccfc + languageName: node + linkType: hard + +"express-response-size@npm:^0.0.3": + version: 0.0.3 + resolution: "express-response-size@npm:0.0.3" + dependencies: + on-headers: "npm:1.0.1" + checksum: 10/6c97395d225e5aa98338569842ed84e5d861f19f6f1c535e70c97d65ffe7301826299607f491c824f240952b39b4b18c3269ed3652ecb8bd0abb93c7ece7048b + languageName: node + linkType: hard + +"express-winston@npm:^4.2.0": + version: 4.2.0 + resolution: "express-winston@npm:4.2.0" + dependencies: + chalk: "npm:^2.4.2" + lodash: "npm:^4.17.21" + peerDependencies: + winston: ">=3.x <4" + checksum: 10/3a4fb701d81b75815ccdf19f93585adb3af7ad61b4f67e435bb324486d9e3773e85e8761fb1e4c3833b0a493f363c792e8688eba018d1920fd3ee6d2505e5b3a + languageName: node + linkType: hard + +"express@npm:4.20.0": + version: 4.20.0 + resolution: "express@npm:4.20.0" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.6.0" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.2.0" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.10" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.11.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.19.0" + serve-static: "npm:1.16.0" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 10/4131f566cf8f6d1611475d5ff5d0dbc5c628ad8b525aa2aa2b3da9a23a041efcce09ede10b8a31315b0258ac4e53208a009fd7669ee1eb385936a0d54adb3cde + languageName: node + linkType: hard + "ext-list@npm:^2.0.0": version: 2.2.2 resolution: "ext-list@npm:2.2.2" @@ -9951,6 +10353,13 @@ __metadata: languageName: node linkType: hard +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 10/534ce630c8f63c116292145607fc18c0f06bfa2fd74094357bf65daacc5d3f4f2b285bf8eb112c3bbf98c5caa6d386cced797f44b9b1b33da0c0a81020444826 + languageName: node + linkType: hard + "fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": version: 3.2.0 resolution: "fetch-blob@npm:3.2.0" @@ -10034,6 +10443,21 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:1.2.0": + version: 1.2.0 + resolution: "finalhandler@npm:1.2.0" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:2.0.1" + unpipe: "npm:~1.0.0" + checksum: 10/635718cb203c6d18e6b48dfbb6c54ccb08ea470e4f474ddcef38c47edcf3227feec316f886dd701235997d8af35240cae49856721ce18f539ad038665ebbf163 + languageName: node + linkType: hard + "find-up@npm:^2.0.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" @@ -10080,6 +10504,13 @@ __metadata: languageName: node linkType: hard +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 10/000198af190ae02f0138ac5fa4310da733224c628e0230c81e3fff7c4e094af7e0e8bb9f4357cabd21db601759d89f3445da744afbae20623cfa41edf3888397 + languageName: node + linkType: hard + "focus-visible@npm:^4.1.5": version: 4.1.5 resolution: "focus-visible@npm:4.1.5" @@ -10087,6 +10518,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.6": + version: 1.15.9 + resolution: "follow-redirects@npm:1.15.9" + peerDependenciesMeta: + debug: + optional: true + checksum: 10/e3ab42d1097e90d28b913903841e6779eb969b62a64706a3eb983e894a5db000fbd89296f45f08885a0e54cd558ef62e81be1165da9be25a6c44920da10f424c + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -10137,6 +10578,20 @@ __metadata: languageName: node linkType: hard +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10/29ba9fd347117144e97cbb8852baae5e8b2acb7d1b591ef85695ed96f5b933b1804a7fac4a15dd09ca7ac7d0cdc104410e8102aae2dd3faa570a797ba07adb81 + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1 + languageName: node + linkType: hard + "fs-constants@npm:^1.0.0": version: 1.0.0 resolution: "fs-constants@npm:1.0.0" @@ -10321,6 +10776,23 @@ __metadata: languageName: node linkType: hard +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: "npm:^1.0.3 || ^2.0.0" + color-support: "npm:^1.1.2" + console-control-strings: "npm:^1.0.0" + has-unicode: "npm:^2.0.1" + object-assign: "npm:^4.1.1" + signal-exit: "npm:^3.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wide-align: "npm:^1.1.2" + checksum: 10/46df086451672a5fecd58f7ec86da74542c795f8e00153fbef2884286ce0e86653c3eb23be2d0abb0c4a82b9b2a9dec3b09b6a1cf31c28085fa0376599a26589 + languageName: node + linkType: hard + "gauge@npm:^4.0.3": version: 4.0.4 resolution: "gauge@npm:4.0.4" @@ -10918,6 +11390,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10/0e7f76ee8ff8a33e58a3281a469815b893c41357378f408be8f6d4aa7d1efafb0da064625518e7078381b6a92325949b119dc38fcb30bdbc4e3a35f78c44c439 + languageName: node + linkType: hard + "http-proxy-agent@npm:^4.0.1": version: 4.0.1 resolution: "http-proxy-agent@npm:4.0.1" @@ -11160,7 +11645,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -11249,6 +11734,13 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10/864d0cced0c0832700e9621913a6429ccdc67f37c1bd78fb8c6789fff35c9d167cb329134acad2290497a53336813ab4798d2794fd675d5eb33b5fdf0982b9ca + languageName: node + linkType: hard + "is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" @@ -11276,6 +11768,13 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10/81a78d518ebd8b834523e25d102684ee0f7e98637136d3bdc93fd09636350fa06f1d8ca997ea28143d4d13cb1b69c0824f082db0ac13e1ab3311c10ffea60ade + languageName: node + linkType: hard + "is-async-function@npm:^2.0.0": version: 2.0.0 resolution: "is-async-function@npm:2.0.0" @@ -12846,6 +13345,27 @@ __metadata: languageName: node linkType: hard +"jwa@npm:^2.0.0": + version: 2.0.0 + resolution: "jwa@npm:2.0.0" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10/ab983f6685d99d13ddfbffef9b1c66309a536362a8412d49ba6e687d834a1240ce39290f30ac7dbe241e0ab6c76fee7ff795776ce534e11d148158c9b7193498 + languageName: node + linkType: hard + +"jws@npm:^4.0.0": + version: 4.0.0 + resolution: "jws@npm:4.0.0" + dependencies: + jwa: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + checksum: 10/1d15f4cdea376c6bd6a81002bd2cb0bf3d51d83da8f0727947b5ba3e10cf366721b8c0d099bf8c1eb99eb036e2c55e5fd5efd378ccff75a2b4e0bd10002348b9 + languageName: node + linkType: hard + "keyv@npm:^4.0.0": version: 4.5.2 resolution: "keyv@npm:4.5.2" @@ -12876,6 +13396,13 @@ __metadata: languageName: node linkType: hard +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 10/9e10b5a1659f9ed8761d38df3c35effabffbd19fc6107324095238e4ef0ff044392cae9ac64a1c2dda26e532426485342226b93806bd97504b174b0dcf04ed81 + languageName: node + linkType: hard + "language-subtag-registry@npm:^0.3.20": version: 0.3.23 resolution: "language-subtag-registry@npm:0.3.23" @@ -13117,6 +13644,20 @@ __metadata: languageName: node linkType: hard +"logform@npm:^2.6.0, logform@npm:^2.6.1": + version: 2.6.1 + resolution: "logform@npm:2.6.1" + dependencies: + "@colors/colors": "npm:1.6.0" + "@types/triple-beam": "npm:^1.3.2" + fecha: "npm:^4.2.0" + ms: "npm:^2.1.1" + safe-stable-stringify: "npm:^2.3.1" + triple-beam: "npm:^1.3.0" + checksum: 10/e67f414787fbfe1e6a997f4c84300c7e06bee3d0bd579778af667e24b36db3ea200ed195d41b61311ff738dab7faabc615a07b174b22fe69e0b2f39e985be64b + languageName: node + linkType: hard + "longest-streak@npm:^3.0.0": version: 3.1.0 resolution: "longest-streak@npm:3.1.0" @@ -13313,7 +13854,7 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.0.0": +"make-dir@npm:^3.0.0, make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -13622,6 +14163,13 @@ __metadata: languageName: node linkType: hard +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10/38e0984db39139604756903a01397e29e17dcb04207bb3e081412ce725ab17338ecc47220c1b186b6bbe79a658aad1b0d41142884f5a481f36290cdefbe6aa46 + languageName: node + linkType: hard + "mem@npm:^1.1.0": version: 1.1.0 resolution: "mem@npm:1.1.0" @@ -13654,6 +14202,13 @@ __metadata: languageName: node linkType: hard +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10/52117adbe0313d5defa771c9993fe081e2d2df9b840597e966aadafde04ae8d0e3da46bac7ca4efc37d4d2b839436582659cd49c6a43eacb3fe3050896a105d1 + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -13668,6 +14223,13 @@ __metadata: languageName: node linkType: hard +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 10/a385dd974faa34b5dd021b2bbf78c722881bf6f003bfe6d391d7da3ea1ed625d1ff10ddd13c57531f628b3e785be38d3eed10ad03cebd90b76932413df9a1820 + languageName: node + linkType: hard + "micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": version: 1.1.0 resolution: "micromark-core-commonmark@npm:1.1.0" @@ -14007,6 +14569,29 @@ __metadata: languageName: node linkType: hard +"migrate@npm:^2.0.1": + version: 2.1.0 + resolution: "migrate@npm:2.1.0" + dependencies: + chalk: "npm:^4.1.2" + commander: "npm:^2.20.3" + dateformat: "npm:^4.6.3" + dotenv: "npm:^16.0.0" + inherits: "npm:^2.0.3" + minimatch: "npm:^9.0.1" + mkdirp: "npm:^3.0.1" + slug: "npm:^8.2.2" + bin: + migrate: bin/migrate + migrate-create: bin/migrate-create + migrate-down: bin/migrate-down + migrate-init: bin/migrate-init + migrate-list: bin/migrate-list + migrate-up: bin/migrate-up + checksum: 10/13aabd8f018053f8db079925b02da808efc196150e0f28d536752e4dab2c81d4cb9dc69fb54268bfa2b86e79ce5241a6b6514bcbdc9250a4d71e065f61f774ae + languageName: node + linkType: hard + "mime-db@npm:1.52.0, mime-db@npm:^1.28.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -14014,7 +14599,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27": +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -14023,6 +14608,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: 10/b7d98bb1e006c0e63e2c91b590fe1163b872abf8f7ef224d53dd31499c2197278a6d3d0864c45239b1a93d22feaf6f9477e9fc847eef945838150b8c02d03170 + languageName: node + linkType: hard + "mime@npm:^2.5.2": version: 2.6.0 resolution: "mime@npm:2.6.0" @@ -14230,6 +14824,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10/16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba + languageName: node + linkType: hard + "mktemp@npm:~0.4.0": version: 0.4.0 resolution: "mktemp@npm:0.4.0" @@ -14284,7 +14887,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -14328,7 +14931,7 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:^0.6.3": +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" checksum: 10/2723fb822a17ad55c93a588a4bc44d53b22855bf4be5499916ca0cab1e7165409d0b288ba2577d7b029f10ce18cf2ed8e703e5af31c984e1e2304277ef979837 @@ -14377,6 +14980,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/595f59ffb4630564f587c502119cbd980d302e482781021f3b479f5fc7e41cf8f2f7280fdc2795f32d148e4f3259bd15043c52d4a3442796aa6f1ae97b959636 + languageName: node + linkType: hard + "node-api-version@npm:^0.2.0": version: 0.2.0 resolution: "node-api-version@npm:0.2.0" @@ -14412,7 +15024,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.7.0": +"node-fetch@npm:^2.6.7, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -14516,6 +15128,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:^5.0.0": + version: 5.0.0 + resolution: "nopt@npm:5.0.0" + dependencies: + abbrev: "npm:1" + bin: + nopt: bin/nopt.js + checksum: 10/00f9bb2d16449469ba8ffcf9b8f0eae6bae285ec74b135fec533e5883563d2400c0cd70902d0a7759e47ac031ccf206ace4e86556da08ed3f1c66dda206e9ccd + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -14527,6 +15150,16 @@ __metadata: languageName: node linkType: hard +"nordigen-node@npm:^1.4.0": + version: 1.4.0 + resolution: "nordigen-node@npm:1.4.0" + dependencies: + axios: "npm:^1.2.1" + dotenv: "npm:^10.0.0" + checksum: 10/88def53ba66468f4ac05ec12c6958eb87c20907a51bfc1f02b974ac3d9efcc61e67e4e9408c3d008703ad3ae9723564b5c08a627c4d3ac412cea4fb7ff50d6f2 + languageName: node + linkType: hard + "normalize-package-data@npm:^2.3.2": version: 2.5.0 resolution: "normalize-package-data@npm:2.5.0" @@ -14620,6 +15253,18 @@ __metadata: languageName: node linkType: hard +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: "npm:^2.0.0" + console-control-strings: "npm:^1.1.0" + gauge: "npm:^3.0.0" + set-blocking: "npm:^2.0.0" + checksum: 10/f42c7b9584cdd26a13c41a21930b6f5912896b6419ab15be88cc5721fc792f1c3dd30eb602b26ae08575694628ba70afdcf3675d86e4f450fc544757e52726ec + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -14655,7 +15300,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": +"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -14743,6 +15388,22 @@ __metadata: languageName: node linkType: hard +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10/8e81472c5028125c8c39044ac4ab8ba51a7cdc19a9fbd4710f5d524a74c6d8c9ded4dd0eed83f28d3d33ac1d7a6a439ba948ccb765ac6ce87f30450a26bfe2ea + languageName: node + linkType: hard + +"on-headers@npm:1.0.1": + version: 1.0.1 + resolution: "on-headers@npm:1.0.1" + checksum: 10/7e5dc811cd8e16590385ac56a63e27aa9006c594069960655d37f96c9b1f7af4ce7e71f4f6a771ed746ebf180e0c1cbc1e5ecd125a4006b9eb0ef23125068cd2 + languageName: node + linkType: hard + "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -14752,6 +15413,15 @@ __metadata: languageName: node linkType: hard +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: "npm:1.x.x" + checksum: 10/64d0160480eeae4e3b2a6fc0a02f452e05bb0cc8373a4ed56a4fc08c3939dcb91bc20075003ed499655bd16919feb63ca56f86eee7932c5251f7d629b55dfc90 + languageName: node + linkType: hard + "onetime@npm:^5.1.0, onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" @@ -15069,6 +15739,13 @@ __metadata: languageName: node linkType: hard +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 + languageName: node + linkType: hard + "path-browserify@npm:^1.0.1": version: 1.0.1 resolution: "path-browserify@npm:1.0.1" @@ -15142,6 +15819,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:0.1.10": + version: 0.1.10 + resolution: "path-to-regexp@npm:0.1.10" + checksum: 10/894e31f1b20e592732a87db61fff5b95c892a3fe430f9ab18455ebe69ee88ef86f8eb49912e261f9926fc53da9f93b46521523e33aefd9cb0a7b0d85d7096006 + languageName: node + linkType: hard + "path-type@npm:^2.0.0": version: 2.0.0 resolution: "path-type@npm:2.0.0" @@ -15539,6 +16223,15 @@ __metadata: languageName: node linkType: hard +"properties-reader@npm:^2.2.0": + version: 2.3.0 + resolution: "properties-reader@npm:2.3.0" + dependencies: + mkdirp: "npm:^1.0.4" + checksum: 10/0b41eb4136dc278ae0d97968ccce8de2d48d321655b319192e31f2424f1c6e052182204671e65aa8967216360cb3e7cbd9129830062e058fe9d6a1d74964c29a + languageName: node + linkType: hard + "property-information@npm:^6.0.0": version: 6.2.0 resolution: "property-information@npm:6.2.0" @@ -15553,6 +16246,23 @@ __metadata: languageName: node linkType: hard +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10/f24a0c80af0e75d31e3451398670d73406ec642914da11a2965b80b1898ca6f66a0e3e091a11a4327079b2b268795f6fa06691923fef91887215c3d0e8ea3f68 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10/f0bb4a87cfd18f77bc2fba23ae49c3b378fb35143af16cc478171c623eebe181678f09439707ad80081d340d1593cd54a33a0113f3ccb3f4bc9451488780ee23 + languageName: node + linkType: hard + "pseudomap@npm:^1.0.2": version: 1.0.2 resolution: "pseudomap@npm:1.0.2" @@ -15591,6 +16301,24 @@ __metadata: languageName: node linkType: hard +"qs@npm:6.11.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" + dependencies: + side-channel: "npm:^1.0.4" + checksum: 10/5a3bfea3e2f359ede1bfa5d2f0dbe54001aa55e40e27dc3e60fab814362d83a9b30758db057c2011b6f53a2d4e4e5150194b5bac45372652aecb3e3c0d4b256e + languageName: node + linkType: hard + +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10/f548b376e685553d12e461409f0d6e5c59ec7c7d76f308e2a888fd9db3e0c5e89902bedd0754db3a9038eda5f27da2331a6f019c8517dc5e0a16b3c9a6e9cef8 + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -15646,6 +16374,25 @@ __metadata: languageName: node linkType: hard +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10/ce21ef2a2dd40506893157970dc76e835c78cf56437e26e19189c48d5291e7279314477b06ac38abd6a401b661a6840f7b03bd0b1249da9b691deeaa15872c26 + languageName: node + linkType: hard + +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 10/863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 + languageName: node + linkType: hard + "rc4@npm:~0.1.5": version: 0.1.5 resolution: "rc4@npm:0.1.5" @@ -16196,6 +16943,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.5.2": + version: 4.5.2 + resolution: "readable-stream@npm:4.5.2" + dependencies: + abort-controller: "npm:^3.0.0" + buffer: "npm:^6.0.3" + events: "npm:^3.3.0" + process: "npm:^0.11.10" + string_decoder: "npm:^1.3.0" + checksum: 10/01b128a559c5fd76a898495f858cf0a8839f135e6a69e3409f986e88460134791657eb46a2ff16826f331682a3c4d0c5a75cef5e52ef259711021ba52b1c2e82 + languageName: node + linkType: hard + "readable-stream@npm:~1.0.31": version: 1.0.34 resolution: "readable-stream@npm:1.0.34" @@ -16814,6 +17574,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.3.1": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10/2697fa186c17c38c3ca5309637b4ac6de2f1c3d282da27cd5e1e3c88eca0fb1f9aea568a6aabdf284111592c8782b94ee07176f17126031be72ab1313ed46c5c + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -16925,6 +17692,48 @@ __metadata: languageName: node linkType: hard +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10/ec66c0ad109680ad8141d507677cfd8b4e40b9559de23191871803ed241718e99026faa46c398dcfb9250676076573bd6bfe5d0ec347f88f4b7b8533d1d391cb + languageName: node + linkType: hard + +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10/1f6064dea0ae4cbe4878437aedc9270c33f2a6650a77b56a16b62d057527f2766d96ee282997dd53ec0339082f2aad935bc7d989b46b48c82fc610800dc3a1d0 + languageName: node + linkType: hard + "serialize-error@npm:^7.0.1": version: 7.0.1 resolution: "serialize-error@npm:7.0.1" @@ -16943,6 +17752,18 @@ __metadata: languageName: node linkType: hard +"serve-static@npm:1.16.0": + version: 1.16.0 + resolution: "serve-static@npm:1.16.0" + dependencies: + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.18.0" + checksum: 10/29a01f67e8c64a359d49dd0c46bc95bb4aa99781f97845dccbf0c8cd0284c5fd79ad7fb9433a36fac4b6c58b577d3eab314a379142412413b8b5cd73be3cd551 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -16983,6 +17804,13 @@ __metadata: languageName: node linkType: hard +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e + languageName: node + linkType: hard + "shallow-clone@npm:^3.0.0": version: 3.0.1 resolution: "shallow-clone@npm:3.0.1" @@ -17082,6 +17910,15 @@ __metadata: languageName: node linkType: hard +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10/c6dffff17aaa383dae7e5c056fbf10cf9855a9f79949f20ee225c04f06ddde56323600e0f3d6797e82d08d006e93761122527438ee9531620031c08c9e0d73cc + languageName: node + linkType: hard + "simple-update-notifier@npm:2.0.0": version: 2.0.0 resolution: "simple-update-notifier@npm:2.0.0" @@ -17154,6 +17991,15 @@ __metadata: languageName: node linkType: hard +"slug@npm:^8.2.2": + version: 8.2.3 + resolution: "slug@npm:8.2.3" + bin: + slug: cli.js + checksum: 10/341e87b07fd89e2947cb7016e02fdf7a2280536b88715ba5318aa18dc4bfb18052037da867c512999d8cd7b14a50743c78f6604ae2d4aa9d3b0f289043ed5c3c + languageName: node + linkType: hard + "smart-buffer@npm:^4.0.2, smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -17366,6 +18212,13 @@ __metadata: languageName: node linkType: hard +"stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 10/7bd633f0e9ac46e81a0b0fe6538482c1d77031959cf94478228731709db4672fbbed59176f5b9a9fd89fec656b5dae03d084ef2d1b0c4c2f5683e05f2dbb1405 + languageName: node + linkType: hard + "stack-utils@npm:^2.0.3": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" @@ -17389,6 +18242,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10/18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb + languageName: node + linkType: hard + "std-env@npm:^3.5.0": version: 3.6.0 resolution: "std-env@npm:3.6.0" @@ -17605,7 +18465,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -18073,6 +18933,13 @@ __metadata: languageName: node linkType: hard +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 10/1138f68adc97bf4381a302a24e2352f04992b7b1316c5003767e9b0d3367ffd0dc73d65001ea02b07cd0ecc2a9d186de0cf02f3c2d880b8a522d4ccb9342244a + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -18203,6 +19070,13 @@ __metadata: languageName: node linkType: hard +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 + languageName: node + linkType: hard + "totalist@npm:^3.0.0": version: 3.0.1 resolution: "totalist@npm:3.0.1" @@ -18270,6 +19144,13 @@ __metadata: languageName: node linkType: hard +"triple-beam@npm:^1.3.0": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 10/2e881a3e8e076b6f2b85b9ec9dd4a900d3f5016e6d21183ed98e78f9abcc0149e7d54d79a3f432b23afde46b0885bdcdcbff789f39bc75de796316961ec07f61 + languageName: node + linkType: hard + "trough@npm:^2.0.0": version: 2.1.0 resolution: "trough@npm:2.1.0" @@ -18464,6 +19345,16 @@ __metadata: languageName: node linkType: hard +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 10/0bd9eeae5efd27d98fd63519f999908c009e148039d8e7179a074f105362d4fcc214c38b24f6cda79c87e563cbd12083a4691381ed28559220d4a10c2047bed4 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.2": version: 1.0.2 resolution: "typed-array-buffer@npm:1.0.2" @@ -18854,6 +19745,13 @@ __metadata: languageName: node linkType: hard +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 + languageName: node + linkType: hard + "untildify@npm:^4.0.0": version: 4.0.0 resolution: "untildify@npm:4.0.0" @@ -18964,6 +19862,13 @@ __metadata: languageName: node linkType: hard +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 10/5d6949693d58cb2e636a84f3ee1c6e7b2f9c16cb1d42d0ecb386d8c025c69e327205aa1c69e2868cc06a01e5e20681fbba55a4e0ed0cce913d60334024eae798 + languageName: node + linkType: hard + "uuid@npm:^3.0.1, uuid@npm:^3.3.2": version: 3.4.0 resolution: "uuid@npm:3.4.0" @@ -18973,7 +19878,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^9.0.1": +"uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" bin: @@ -19031,6 +19936,13 @@ __metadata: languageName: node linkType: hard +"vary@npm:^1, vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10/31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242 + languageName: node + linkType: hard + "verror@npm:^1.10.0": version: 1.10.1 resolution: "verror@npm:1.10.1" @@ -19659,7 +20571,7 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.5": +"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" dependencies: @@ -19675,6 +20587,36 @@ __metadata: languageName: node linkType: hard +"winston-transport@npm:^4.7.0": + version: 4.8.0 + resolution: "winston-transport@npm:4.8.0" + dependencies: + logform: "npm:^2.6.1" + readable-stream: "npm:^4.5.2" + triple-beam: "npm:^1.3.0" + checksum: 10/930bdc0ec689d5c4f07a262721da80440336f64739d0ce33db801c7142b4fca5be8ef71b725b670bac609de8b6bce405e5c5f84d355f5176a611209b476cee18 + languageName: node + linkType: hard + +"winston@npm:^3.14.2": + version: 3.15.0 + resolution: "winston@npm:3.15.0" + dependencies: + "@colors/colors": "npm:^1.6.0" + "@dabh/diagnostics": "npm:^2.0.2" + async: "npm:^3.2.3" + is-stream: "npm:^2.0.0" + logform: "npm:^2.6.0" + one-time: "npm:^1.0.0" + readable-stream: "npm:^3.4.0" + safe-stable-stringify: "npm:^2.3.1" + stack-trace: "npm:0.0.x" + triple-beam: "npm:^1.3.0" + winston-transport: "npm:^4.7.0" + checksum: 10/60e55eb3621e4de1a764a4e43ee1d242c71957d3e0eb359cb8f16fe2b9d9543fd4c31a8d3baf96fa7e43ef5df383c43c1a98aff4bd714ea0082303504b0e3cdc + languageName: node + linkType: hard + "word-wrap@npm:~1.2.3": version: 1.2.3 resolution: "word-wrap@npm:1.2.3" From 7782f2dd6bb074efb4ed69002b9ebcdfafb3811f Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sat, 12 Oct 2024 13:11:49 +0100 Subject: [PATCH 07/33] ensuring deps are built before running server --- bin/package-electron | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/bin/package-electron b/bin/package-electron index b827d0015a0..9f603b94967 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -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 --mode=desktop yarn workspace desktop-electron update-client diff --git a/package.json b/package.json index 7a673cd67cf..cc58c2d61f9 100644 --- a/package.json +++ b/package.json @@ -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", From 55e91139c8e52b9dd13459149eaf853482d7e9d4 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sat, 12 Oct 2024 13:39:32 +0100 Subject: [PATCH 08/33] unneeded mode --- bin/package-electron | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/package-electron b/bin/package-electron index 9f603b94967..e3934ea84d0 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -37,7 +37,7 @@ fi yarn workspace loot-core build:node yarn workspace @actual-app/web build --mode=desktop -yarn workspace @actual-app/crdt build --mode=desktop +yarn workspace @actual-app/crdt build yarn workspace desktop-electron update-client From 32663197626f010b4f0a5dc2f42b78ab66b54ae3 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sun, 13 Oct 2024 22:19:09 +0100 Subject: [PATCH 09/33] exposing sync server via ngrok --- .../src/browser-preload.browser.js | 1 + .../src/components/manager/BudgetList.tsx | 16 +++++- packages/desktop-electron/index.ts | 44 ++++++++++++++-- packages/desktop-electron/package.json | 1 + packages/desktop-electron/preload.ts | 5 ++ packages/loot-core/typings/window.d.ts | 4 ++ yarn.lock | 52 +++++++++++++++++++ 7 files changed, 119 insertions(+), 4 deletions(-) diff --git a/packages/desktop-client/src/browser-preload.browser.js b/packages/desktop-client/src/browser-preload.browser.js index 43a3037cc92..43f4f0c30e1 100644 --- a/packages/desktop-client/src/browser-preload.browser.js +++ b/packages/desktop-client/src/browser-preload.browser.js @@ -141,6 +141,7 @@ global.Actual = { }, downloadActualServer: () => {}, startActualServer: () => {}, + exposeActualServer: () => {}, onEventFromMain: () => {}, applyAppUpdate: () => {}, updateAppMenu: () => {}, diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 2afadfe3c7f..528f5ba11b6 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -445,6 +445,10 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { await globalThis.Actual.startActualServer('v24.10.1'); }; + const exposeActualServer = async () => { + await globalThis.Actual.exposeActualServer(); + }; + return ( - Start Electron Server + Start Server + + )} diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 6b0f81403b1..47ccf1b53c5 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -17,6 +17,7 @@ import { SaveDialogOptions, } from 'electron'; import { copy, exists, remove } from 'fs-extra'; +import ngrok from 'ngrok'; import promiseRetry from 'promise-retry'; import { getMenu } from './menu'; @@ -405,9 +406,9 @@ ipcMain.handle( ipcMain.handle( 'start-actual-server', async (_event, payload: { releaseVersion: string }) => { - const serverReleaseDir = path.resolve( - `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, - ); + // const serverReleaseDir = path.resolve( + // `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, + // ); // actualServerProcess = utilityProcess.fork( // serverReleaseDir + '/app.js', // if bundling it ourselves and manually including it @@ -443,6 +444,43 @@ ipcMain.handle( clientWin?.webContents.executeJavaScript(` console.error('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); + + const url = ngrok.connect({ + proto: 'http', + addr: 5006, + region: 'us', + }); + + return url; + }, +); + +export type ExposeActualServerPayload = { + region: 'us' | 'eu' | 'au' | 'ap' | 'sa' | 'jp' | 'in'; + port: number; +}; + +ipcMain.handle( + 'expose-actual-server', + async ( + _event, + payload: ExposeActualServerPayload = { + region: 'eu', + port: 5006, + }, + ) => { + try { + const url = await ngrok.connect({ + scheme: 'https', + addr: payload.port, + region: payload.region, + }); + + console.info(`Exposing actual server on url: ${url}`); + return url; + } catch (error) { + console.error('Unable to run ngrok', error); + } }, ); diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 6eaeb555318..9622945adc7 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -79,6 +79,7 @@ "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", + "ngrok": "^5.0.0-beta.2", "node-fetch": "^2.7.0", "promise-retry": "^2.0.1" }, diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index 2c65ddff64e..8b53ed31002 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -1,6 +1,7 @@ import { ipcRenderer, contextBridge, IpcRenderer } from 'electron'; import { + ExposeActualServerPayload, GetBootstrapDataPayload, OpenFileDialogPayload, SaveFileDialogPayload, @@ -70,6 +71,10 @@ contextBridge.exposeInMainWorld('Actual', { }); }, + exposeActualServer: (settings: ExposeActualServerPayload) => { + return ipcRenderer.invoke('expose-actual-server', settings); + }, + onEventFromMain: (type: string, handler: (...args: unknown[]) => void) => { ipcRenderer.on(type, handler); }, diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 805db112ba6..9d95204f18b 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -8,6 +8,10 @@ declare global { openURLInBrowser: (url: string) => void; downloadActualServer: (releaseVersion: string) => Promise; startActualServer: (releaseVersion: string) => Promise; + exposeActualServer: (settings: { + region: 'us' | 'eu' | 'au' | 'ap' | 'sa' | 'jp' | 'in'; + port: number; + }) => Promise; saveFile: ( contents: string | Buffer, filename: string, diff --git a/yarn.lock b/yarn.lock index 439310d4fe2..087745d4df7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8683,6 +8683,7 @@ __metadata: electron: "npm:30.0.6" electron-builder: "npm:24.13.3" fs-extra: "npm:^11.2.0" + ngrok: "npm:^5.0.0-beta.2" node-fetch: "npm:^2.7.0" promise-retry: "npm:^2.0.1" typescript: "npm:^5.5.4" @@ -11346,6 +11347,13 @@ __metadata: languageName: node linkType: hard +"hpagent@npm:^0.1.2": + version: 0.1.2 + resolution: "hpagent@npm:0.1.2" + checksum: 10/bd033b3700bb523edc9a805f8683c71fddd622df901e73842b5e3357136ce062c2ddb2ab5e9f5b3d84e0977bfe439f5cdc51d755a11e99376eb95e4624312f0a + languageName: node + linkType: hard + "html-encoding-sniffer@npm:^2.0.1": version: 2.0.1 resolution: "html-encoding-sniffer@npm:2.0.1" @@ -13593,6 +13601,13 @@ __metadata: languageName: node linkType: hard +"lodash.clonedeep@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.clonedeep@npm:4.5.0" + checksum: 10/957ed243f84ba6791d4992d5c222ffffca339a3b79dbe81d2eaf0c90504160b500641c5a0f56e27630030b18b8e971ea10b44f928a977d5ced3c8948841b555f + languageName: node + linkType: hard + "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -14945,6 +14960,25 @@ __metadata: languageName: node linkType: hard +"ngrok@npm:^5.0.0-beta.2": + version: 5.0.0-beta.2 + resolution: "ngrok@npm:5.0.0-beta.2" + dependencies: + extract-zip: "npm:^2.0.1" + got: "npm:^11.8.5" + hpagent: "npm:^0.1.2" + lodash.clonedeep: "npm:^4.5.0" + uuid: "npm:^7.0.0 || ^8.0.0" + yaml: "npm:^2.2.2" + dependenciesMeta: + hpagent: + optional: true + bin: + ngrok: bin/ngrok + checksum: 10/46712d4e59cc8bc9d3bbfa8c327ed12f5ea218991c26fddf22d9c9d4eea59d2caeb6463790a8daceaafe76223c07ecd27297ce7fc31cd88d9cea788945f315b7 + languageName: node + linkType: hard + "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -19878,6 +19912,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^7.0.0 || ^8.0.0": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 10/9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 + languageName: node + linkType: hard + "uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" @@ -20988,6 +21031,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.2.2": + version: 2.6.0 + resolution: "yaml@npm:2.6.0" + bin: + yaml: bin.mjs + checksum: 10/f4369f667c7626c216ea81b5840fe9b530cdae4cff2d84d166ec1239e54bf332dbfac4a71bf60d121f8e85e175364a4e280a520292269b6cf9d074368309adf9 + languageName: node + linkType: hard + "yaml@npm:~2.5.0": version: 2.5.0 resolution: "yaml@npm:2.5.0" From b3e6d28136ddfe1e4df1268e3284b1165dddd3a0 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sun, 13 Oct 2024 23:07:00 +0100 Subject: [PATCH 10/33] log msg for dev --- packages/desktop-client/src/components/manager/BudgetList.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 528f5ba11b6..f2759360877 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -446,7 +446,8 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { }; const exposeActualServer = async () => { - await globalThis.Actual.exposeActualServer(); + const url = await globalThis.Actual.exposeActualServer(); + console.info('exposting actual at: ' + url); }; return ( From ce5225e593d51eccda55c30905238aeca95e37ad Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 14:13:57 +0100 Subject: [PATCH 11/33] using official ngrok package for smaller size --- .../src/components/manager/BudgetList.tsx | 7 +- packages/desktop-electron/index.ts | 26 ++- packages/desktop-electron/package.json | 2 +- packages/loot-core/src/server/main.ts | 6 + packages/loot-core/src/types/prefs.d.ts | 1 + packages/loot-core/typings/window.d.ts | 2 +- yarn.lock | 192 +++++++++++++----- 7 files changed, 167 insertions(+), 69 deletions(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index f2759360877..49707a1936a 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -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'; @@ -445,8 +446,12 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { await globalThis.Actual.startActualServer('v24.10.1'); }; + const [ngrokAuthToken] = useGlobalPref('ngrokAuthToken'); const exposeActualServer = async () => { - const url = await globalThis.Actual.exposeActualServer(); + const url = await globalThis.Actual.exposeActualServer({ + authToken: ngrokAuthToken, + port: 5006, + }); console.info('exposting actual at: ' + url); }; diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 47ccf1b53c5..da824d9615e 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import path from 'path'; +import ngrok from '@ngrok/ngrok'; import { net, app, @@ -17,7 +18,6 @@ import { SaveDialogOptions, } from 'electron'; import { copy, exists, remove } from 'fs-extra'; -import ngrok from 'ngrok'; import promiseRetry from 'promise-retry'; import { getMenu } from './menu'; @@ -456,28 +456,26 @@ ipcMain.handle( ); export type ExposeActualServerPayload = { - region: 'us' | 'eu' | 'au' | 'ap' | 'sa' | 'jp' | 'in'; + authToken: string; port: number; }; ipcMain.handle( 'expose-actual-server', - async ( - _event, - payload: ExposeActualServerPayload = { - region: 'eu', - port: 5006, - }, - ) => { + async (_event, payload: ExposeActualServerPayload) => { try { - const url = await ngrok.connect({ - scheme: 'https', + 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: payload.port, - region: payload.region, + host_header: `localhost:${payload.port}`, + authtoken: payload.authToken, + domain: 'creative-genuinely-meerkat.ngrok-free.app', // should come from settings? + // crt: fs.readFileSync("crt.pem", "utf8"), + // key: fs.readFileSync("key.pem", "utf8"), }); - console.info(`Exposing actual server on url: ${url}`); - return url; + console.info(`Exposing actual server on url: ${listener.url()}`); + return listener.url(); } catch (error) { console.error('Unable to run ngrok', error); } diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 9622945adc7..584cc16a4de 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -75,11 +75,11 @@ "npmRebuild": false }, "dependencies": { + "@ngrok/ngrok": "^1.4.1", "actual-sync": "file:../../../actual-server", "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", - "ngrok": "^5.0.0-beta.2", "node-fetch": "^2.7.0", "promise-retry": "^2.0.1" }, diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index a2a547173af..4d8fe958a82 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1262,6 +1262,9 @@ handlers['save-global-prefs'] = async function (prefs) { prefs.serverSelfSignedCert, ); } + if ('ngrokAuthToken' in prefs) { + await asyncStorage.setItem('ngrokAuthToken', prefs.ngrokAuthToken); + } return 'ok'; }; @@ -1274,6 +1277,7 @@ handlers['load-global-prefs'] = async function () { [, theme], [, preferredDarkTheme], [, serverSelfSignedCert], + [, ngrokAuthToken], ] = await asyncStorage.multiGet([ 'floating-sidebar', 'max-months', @@ -1282,6 +1286,7 @@ handlers['load-global-prefs'] = async function () { 'theme', 'preferred-dark-theme', 'server-self-signed-cert', + 'ngrokAuthToken', ]); return { floatingSidebar: floatingSidebar === 'true' ? true : false, @@ -1301,6 +1306,7 @@ handlers['load-global-prefs'] = async function () { ? preferredDarkTheme : 'dark', serverSelfSignedCert: serverSelfSignedCert || undefined, + ngrokAuthToken: ngrokAuthToken || undefined, }; }; diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index 036d609f2d4..05240fc2ca7 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -78,4 +78,5 @@ export type GlobalPrefs = Partial<{ preferredDarkTheme: DarkTheme; documentDir: string; // Electron only serverSelfSignedCert: string; // Electron only + ngrokAuthToken: string; // Electron only }>; diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 9d95204f18b..9f039c1972b 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -9,7 +9,7 @@ declare global { downloadActualServer: (releaseVersion: string) => Promise; startActualServer: (releaseVersion: string) => Promise; exposeActualServer: (settings: { - region: 'us' | 'eu' | 'au' | 'ap' | 'sa' | 'jp' | 'in'; + authToken: string; port: number; }) => Promise; saveFile: ( diff --git a/yarn.lock b/yarn.lock index 087745d4df7..79baabcfe72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2472,6 +2472,145 @@ __metadata: languageName: node linkType: hard +"@ngrok/ngrok-android-arm-eabi@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-android-arm-eabi@npm:1.4.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@ngrok/ngrok-android-arm64@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-android-arm64@npm:1.4.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@ngrok/ngrok-darwin-arm64@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-darwin-arm64@npm:1.4.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@ngrok/ngrok-darwin-universal@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-darwin-universal@npm:1.4.1" + conditions: os=darwin + languageName: node + linkType: hard + +"@ngrok/ngrok-darwin-x64@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-darwin-x64@npm:1.4.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@ngrok/ngrok-freebsd-x64@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-freebsd-x64@npm:1.4.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-arm-gnueabihf@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-arm-gnueabihf@npm:1.4.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-arm64-gnu@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-arm64-gnu@npm:1.4.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-arm64-musl@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-arm64-musl@npm:1.4.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-x64-gnu@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-x64-gnu@npm:1.4.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@ngrok/ngrok-linux-x64-musl@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-linux-x64-musl@npm:1.4.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@ngrok/ngrok-win32-ia32-msvc@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-win32-ia32-msvc@npm:1.4.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@ngrok/ngrok-win32-x64-msvc@npm:1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok-win32-x64-msvc@npm:1.4.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@ngrok/ngrok@npm:^1.4.1": + version: 1.4.1 + resolution: "@ngrok/ngrok@npm:1.4.1" + dependencies: + "@ngrok/ngrok-android-arm-eabi": "npm:1.4.1" + "@ngrok/ngrok-android-arm64": "npm:1.4.1" + "@ngrok/ngrok-darwin-arm64": "npm:1.4.1" + "@ngrok/ngrok-darwin-universal": "npm:1.4.1" + "@ngrok/ngrok-darwin-x64": "npm:1.4.1" + "@ngrok/ngrok-freebsd-x64": "npm:1.4.1" + "@ngrok/ngrok-linux-arm-gnueabihf": "npm:1.4.1" + "@ngrok/ngrok-linux-arm64-gnu": "npm:1.4.1" + "@ngrok/ngrok-linux-arm64-musl": "npm:1.4.1" + "@ngrok/ngrok-linux-x64-gnu": "npm:1.4.1" + "@ngrok/ngrok-linux-x64-musl": "npm:1.4.1" + "@ngrok/ngrok-win32-ia32-msvc": "npm:1.4.1" + "@ngrok/ngrok-win32-x64-msvc": "npm:1.4.1" + dependenciesMeta: + "@ngrok/ngrok-android-arm-eabi": + optional: true + "@ngrok/ngrok-android-arm64": + optional: true + "@ngrok/ngrok-darwin-arm64": + optional: true + "@ngrok/ngrok-darwin-universal": + optional: true + "@ngrok/ngrok-darwin-x64": + optional: true + "@ngrok/ngrok-freebsd-x64": + optional: true + "@ngrok/ngrok-linux-arm-gnueabihf": + optional: true + "@ngrok/ngrok-linux-arm64-gnu": + optional: true + "@ngrok/ngrok-linux-arm64-musl": + optional: true + "@ngrok/ngrok-linux-x64-gnu": + optional: true + "@ngrok/ngrok-linux-x64-musl": + optional: true + "@ngrok/ngrok-win32-ia32-msvc": + optional: true + "@ngrok/ngrok-win32-x64-msvc": + optional: true + checksum: 10/c86956756af6eb9f2cc47aba19ac14f59a0d031defc52ee6845b17363b051213abd29c984f11501d88a482f343fbadb0e7bf855803068bceb96bf5fb16dfb2cb + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -8673,6 +8812,7 @@ __metadata: dependencies: "@electron/notarize": "npm:2.4.0" "@electron/rebuild": "npm:3.6.0" + "@ngrok/ngrok": "npm:^1.4.1" "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" actual-sync: "file:../../../actual-server" @@ -8683,7 +8823,6 @@ __metadata: electron: "npm:30.0.6" electron-builder: "npm:24.13.3" fs-extra: "npm:^11.2.0" - ngrok: "npm:^5.0.0-beta.2" node-fetch: "npm:^2.7.0" promise-retry: "npm:^2.0.1" typescript: "npm:^5.5.4" @@ -11347,13 +11486,6 @@ __metadata: languageName: node linkType: hard -"hpagent@npm:^0.1.2": - version: 0.1.2 - resolution: "hpagent@npm:0.1.2" - checksum: 10/bd033b3700bb523edc9a805f8683c71fddd622df901e73842b5e3357136ce062c2ddb2ab5e9f5b3d84e0977bfe439f5cdc51d755a11e99376eb95e4624312f0a - languageName: node - linkType: hard - "html-encoding-sniffer@npm:^2.0.1": version: 2.0.1 resolution: "html-encoding-sniffer@npm:2.0.1" @@ -13601,13 +13733,6 @@ __metadata: languageName: node linkType: hard -"lodash.clonedeep@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.clonedeep@npm:4.5.0" - checksum: 10/957ed243f84ba6791d4992d5c222ffffca339a3b79dbe81d2eaf0c90504160b500641c5a0f56e27630030b18b8e971ea10b44f928a977d5ced3c8948841b555f - languageName: node - linkType: hard - "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -14960,25 +15085,6 @@ __metadata: languageName: node linkType: hard -"ngrok@npm:^5.0.0-beta.2": - version: 5.0.0-beta.2 - resolution: "ngrok@npm:5.0.0-beta.2" - dependencies: - extract-zip: "npm:^2.0.1" - got: "npm:^11.8.5" - hpagent: "npm:^0.1.2" - lodash.clonedeep: "npm:^4.5.0" - uuid: "npm:^7.0.0 || ^8.0.0" - yaml: "npm:^2.2.2" - dependenciesMeta: - hpagent: - optional: true - bin: - ngrok: bin/ngrok - checksum: 10/46712d4e59cc8bc9d3bbfa8c327ed12f5ea218991c26fddf22d9c9d4eea59d2caeb6463790a8daceaafe76223c07ecd27297ce7fc31cd88d9cea788945f315b7 - languageName: node - linkType: hard - "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -19912,15 +20018,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^7.0.0 || ^8.0.0": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" - bin: - uuid: dist/bin/uuid - checksum: 10/9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 - languageName: node - linkType: hard - "uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" @@ -21031,15 +21128,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.2.2": - version: 2.6.0 - resolution: "yaml@npm:2.6.0" - bin: - yaml: bin.mjs - checksum: 10/f4369f667c7626c216ea81b5840fe9b530cdae4cff2d84d166ec1239e54bf332dbfac4a71bf60d121f8e85e175364a4e280a520292269b6cf9d074368309adf9 - languageName: node - linkType: hard - "yaml@npm:~2.5.0": version: 2.5.0 resolution: "yaml@npm:2.5.0" From 07e7730aed87a4c52016f66f84ce2ad3b6b69ff0 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 14:17:25 +0100 Subject: [PATCH 12/33] comment --- packages/desktop-electron/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index da824d9615e..33ea0e857ed 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -464,6 +464,7 @@ ipcMain.handle( 'expose-actual-server', async (_event, payload: ExposeActualServerPayload) => { try { + // TODO: check global setting for ServerRunning, if true, autostart the server and expose it - probably done inside of initApp (main.ts) 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: payload.port, From b9c14dc82424a8f56b120a1eccbd545fc03a05c9 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 20:39:03 +0100 Subject: [PATCH 13/33] ngrok bits --- .../src/components/manager/BudgetList.tsx | 9 +- packages/desktop-electron/index.ts | 189 ++++++++---------- packages/desktop-electron/package.json | 1 - packages/desktop-electron/preload.ts | 11 +- .../platform/server/asyncStorage/index.d.ts | 4 +- packages/loot-core/src/server/main.ts | 10 +- packages/loot-core/src/types/prefs.d.ts | 8 +- packages/loot-core/typings/window.d.ts | 9 +- 8 files changed, 113 insertions(+), 128 deletions(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 49707a1936a..f4307bc944d 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -446,13 +446,14 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { await globalThis.Actual.startActualServer('v24.10.1'); }; - const [ngrokAuthToken] = useGlobalPref('ngrokAuthToken'); + const [ngrokConfig] = useGlobalPref('ngrokConfig'); const exposeActualServer = async () => { const url = await globalThis.Actual.exposeActualServer({ - authToken: ngrokAuthToken, - port: 5006, + authToken: ngrokConfig.authToken, + port: ngrokConfig.port, + domain: ngrokConfig.domain, }); - console.info('exposting actual at: ' + url); + console.info('exposing actual at: ' + url); }; return ( diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 33ea0e857ed..8afd951a96e 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -20,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, @@ -27,7 +29,6 @@ import { } from './window-state'; import './security'; -import AdmZip from 'adm-zip'; const isDev = !app.isPackaged; // dev mode if not packaged @@ -54,11 +55,28 @@ if (!isDev || !process.env.ACTUAL_DATA_DIR) { let clientWin: BrowserWindow | null; let serverProcess: UtilityProcess | null; let actualServerProcess: UtilityProcess | null; +let globalPrefs: Partial | 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', @@ -96,6 +114,62 @@ 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(settings: GlobalPrefs['ngrokConfig']) { + if (settings?.authToken && settings?.domain && settings?.port) { + 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: settings.port, + host_header: `localhost:${settings.port}`, + authtoken: settings.authToken, + domain: settings.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(); @@ -226,6 +300,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, { @@ -376,111 +458,12 @@ ipcMain.handle('open-external-url', (event, url) => { shell.openExternal(url); }); -// NOTE: We could just bundle it in the package, but it would be a large download - consider it though... -ipcMain.handle( - 'download-actual-server', - async (_event, payload: { releaseVersion: string }) => { - console.info({ payload }); - const downloadUrl = `https://github.com/MikesGlitch/actual-server/releases/download/${payload.releaseVersion}/${payload.releaseVersion}-server-sync-dist.zip`; - - try { - const res = await fetch(downloadUrl); - const arrBuffer = await res.arrayBuffer(); - const zipped = new AdmZip(Buffer.from(arrBuffer)); - console.info( - 'actual-server will be installed here:', - process.env.ACTUAL_DATA_DIR, - ); - zipped.extractAllTo( - process.env.ACTUAL_DATA_DIR + '/actual-server-releases', - true, - false, - ); - } catch (error) { - console.error('Error retrieving actual-server:', error); - throw error; - } - }, -); - -ipcMain.handle( - 'start-actual-server', - async (_event, payload: { releaseVersion: string }) => { - // const serverReleaseDir = path.resolve( - // `${process.env.ACTUAL_DATA_DIR}/actual-server/${payload.releaseVersion}`, - // ); - - // actualServerProcess = utilityProcess.fork( - // serverReleaseDir + '/app.js', // if bundling it ourselves and manually including it - // ['--subprocess'], - // isDev ? { execArgv: ['--inspect'], stdio: 'pipe' } : { stdio: 'pipe' }, - // ); - - 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'))})`); - }); - - const url = ngrok.connect({ - proto: 'http', - addr: 5006, - region: 'us', - }); - - return url; - }, -); - -export type ExposeActualServerPayload = { - authToken: string; - port: number; -}; +ipcMain.handle('start-actual-server', async () => startSyncServer()); ipcMain.handle( 'expose-actual-server', - async (_event, payload: ExposeActualServerPayload) => { - try { - // TODO: check global setting for ServerRunning, if true, autostart the server and expose it - probably done inside of initApp (main.ts) - 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: payload.port, - host_header: `localhost:${payload.port}`, - authtoken: payload.authToken, - domain: 'creative-genuinely-meerkat.ngrok-free.app', // should come from settings? - // crt: fs.readFileSync("crt.pem", "utf8"), - // key: fs.readFileSync("key.pem", "utf8"), - }); - - console.info(`Exposing actual server on url: ${listener.url()}`); - return listener.url(); - } catch (error) { - console.error('Unable to run ngrok', error); - } - }, + async (_event, payload: GlobalPrefs['ngrokConfig']) => + exposeSyncServer(payload), ); ipcMain.on('message', (_event, msg) => { diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 584cc16a4de..bd98e77ba5e 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -77,7 +77,6 @@ "dependencies": { "@ngrok/ngrok": "^1.4.1", "actual-sync": "file:../../../actual-server", - "adm-zip": "^0.5.10", "better-sqlite3": "^9.6.0", "fs-extra": "^11.2.0", "node-fetch": "^2.7.0", diff --git a/packages/desktop-electron/preload.ts b/packages/desktop-electron/preload.ts index 8b53ed31002..30316bbdce7 100644 --- a/packages/desktop-electron/preload.ts +++ b/packages/desktop-electron/preload.ts @@ -1,7 +1,8 @@ import { ipcRenderer, contextBridge, IpcRenderer } from 'electron'; +import { GlobalPrefs } from 'loot-core/types/prefs'; + import { - ExposeActualServerPayload, GetBootstrapDataPayload, OpenFileDialogPayload, SaveFileDialogPayload, @@ -59,19 +60,13 @@ contextBridge.exposeInMainWorld('Actual', { ipcRenderer.invoke('open-external-url', url); }, - downloadActualServer: (releaseVersion: string) => { - return ipcRenderer.invoke('download-actual-server', { - releaseVersion, - }); - }, - startActualServer: (releaseVersion: string) => { return ipcRenderer.invoke('start-actual-server', { releaseVersion, }); }, - exposeActualServer: (settings: ExposeActualServerPayload) => { + exposeActualServer: (settings: GlobalPrefs['ngrokConfig']) => { return ipcRenderer.invoke('expose-actual-server', settings); }, diff --git a/packages/loot-core/src/platform/server/asyncStorage/index.d.ts b/packages/loot-core/src/platform/server/asyncStorage/index.d.ts index 4a189f3a86e..6cd9ac22aa2 100644 --- a/packages/loot-core/src/platform/server/asyncStorage/index.d.ts +++ b/packages/loot-core/src/platform/server/asyncStorage/index.d.ts @@ -1,7 +1,7 @@ export function init(opts?: { persist?: boolean }): void; export type Init = typeof init; -export function getItem(key: string): Promise; +export function getItem(key: string): Promise; export type GetItem = typeof getItem; export function setItem(key: string, value: unknown): void; @@ -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; diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index 4d8fe958a82..958e9d5cc03 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1262,8 +1262,8 @@ handlers['save-global-prefs'] = async function (prefs) { prefs.serverSelfSignedCert, ); } - if ('ngrokAuthToken' in prefs) { - await asyncStorage.setItem('ngrokAuthToken', prefs.ngrokAuthToken); + if ('ngrokConfig' in prefs) { + await asyncStorage.setItem('ngrokConfig', prefs.ngrokConfig); } return 'ok'; }; @@ -1277,7 +1277,7 @@ handlers['load-global-prefs'] = async function () { [, theme], [, preferredDarkTheme], [, serverSelfSignedCert], - [, ngrokAuthToken], + [, ngrokConfig], ] = await asyncStorage.multiGet([ 'floating-sidebar', 'max-months', @@ -1286,7 +1286,7 @@ handlers['load-global-prefs'] = async function () { 'theme', 'preferred-dark-theme', 'server-self-signed-cert', - 'ngrokAuthToken', + 'ngrokConfig', ]); return { floatingSidebar: floatingSidebar === 'true' ? true : false, @@ -1306,7 +1306,7 @@ handlers['load-global-prefs'] = async function () { ? preferredDarkTheme : 'dark', serverSelfSignedCert: serverSelfSignedCert || undefined, - ngrokAuthToken: ngrokAuthToken || undefined, + ngrokConfig: ngrokConfig || undefined, }; }; diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index 05240fc2ca7..01dc865718f 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -78,5 +78,11 @@ export type GlobalPrefs = Partial<{ preferredDarkTheme: DarkTheme; documentDir: string; // Electron only serverSelfSignedCert: string; // Electron only - ngrokAuthToken: string; // Electron only + ngrokConfig?: { + // Electron only + autoStart?: boolean; + authToken?: string; + port?: number; + domain?: string; + }; }>; diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 9f039c1972b..a11819ca8a2 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -1,3 +1,5 @@ +import type { GlobalPrefs } from 'loot-core/types/prefs'; + export {}; declare global { @@ -8,10 +10,9 @@ declare global { openURLInBrowser: (url: string) => void; downloadActualServer: (releaseVersion: string) => Promise; startActualServer: (releaseVersion: string) => Promise; - exposeActualServer: (settings: { - authToken: string; - port: number; - }) => Promise; + exposeActualServer: ( + settings: GlobalPrefs['ngrokConfig'], + ) => Promise<{ url?: string; error?: string } | undefined>; saveFile: ( contents: string | Buffer, filename: string, From c6d2f59d0d9f97705eaaf257040d3a736ce51aac Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 20:46:12 +0100 Subject: [PATCH 14/33] correcting ngrok conditional logic --- packages/desktop-electron/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 8afd951a96e..af456e5ba29 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -145,8 +145,11 @@ function startSyncServer() { }); } -async function exposeSyncServer(settings: GlobalPrefs['ngrokConfig']) { - if (settings?.authToken && settings?.domain && settings?.port) { +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' }; } @@ -154,10 +157,10 @@ async function exposeSyncServer(settings: GlobalPrefs['ngrokConfig']) { 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: settings.port, - host_header: `localhost:${settings.port}`, - authtoken: settings.authToken, - domain: settings.domain, + 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"), }); From 8eb145ffae4bfe1ed20572c27fef03afcdae684b Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 20:54:44 +0100 Subject: [PATCH 15/33] ngrok settings optional --- packages/desktop-electron/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index af456e5ba29..077b7c17051 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -306,7 +306,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs - if (globalPrefs.ngrokConfig.autoStart) { + if (globalPrefs.ngrokConfig?.autoStart) { startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } From 23fdf04cde1c52ed24247ef13b09c27799347b1e Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Mon, 14 Oct 2024 21:30:24 +0100 Subject: [PATCH 16/33] bits --- .../src/components/manager/BudgetList.tsx | 19 +++++++++++++------ packages/desktop-electron/index.ts | 4 +++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index f4307bc944d..eb335c3f652 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -448,12 +448,19 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { 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); + const hasRequiredNgrokSettings = + ngrokConfig?.authToken && ngrokConfig?.port && ngrokConfig?.domain; + if (hasRequiredNgrokSettings) { + const url = await globalThis.Actual.exposeActualServer({ + authToken: ngrokConfig.authToken, + port: ngrokConfig.port, + domain: ngrokConfig.domain, + }); + + console.info('exposing actual at: ' + url); + } else { + console.info('ngrok settings not set'); + } }; return ( diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 077b7c17051..c5594cbc70a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -117,7 +117,9 @@ 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) + isDev + ? '../../../node_modules/actual-sync/app.js' + : '../node_modules/actual-sync/app.js', // Temporary - required because actual-server is in the other repo ); // NOTE: config.json parameters will be relative to THIS directory at the moment - may need a fix? From 58aa35041ea93e57e2a77bde8e3956cfc036e1be Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Tue, 15 Oct 2024 14:58:54 +0100 Subject: [PATCH 17/33] moving server folders --- packages/desktop-electron/index.ts | 89 +++++++++++++++++++++++++----- yarn.lock | 5 +- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index c5594cbc70a..181115d05b9 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -16,8 +16,10 @@ import { UtilityProcess, OpenDialogSyncOptions, SaveDialogOptions, + Env, + ForkOptions, } from 'electron'; -import { copy, exists, remove } from 'fs-extra'; +import { copy, exists, mkdir, remove } from 'fs-extra'; import promiseRetry from 'promise-retry'; import { GlobalPrefs } from 'loot-core/types/prefs'; @@ -115,6 +117,34 @@ function createBackgroundProcess() { } function startSyncServer() { + const syncServerConfig = { + port: 5006, // actual-server seems to only run on 5006 - I can't get it to work on anything else... + ACTUAL_SERVER_DATA_DIR: path.resolve( + process.env.ACTUAL_DATA_DIR, + 'actual-server', + ), + ACTUAL_SERVER_FILES: path.resolve( + process.env.ACTUAL_DATA_DIR, + 'actual-server', + 'server-files', + ), + ACTUAL_USER_FILES: path.resolve( + process.env.ACTUAL_DATA_DIR, + 'actual-server', + 'user-files', + ), + defaultDataDir: path.resolve( + // TODO: There's no env variable for this - may need to add one to sync server + process.env.ACTUAL_DATA_DIR, + 'actual-server', + 'data', + ), + https: { + key: '', + cert: '', + }, + }; + const serverPath = path.resolve( __dirname, isDev @@ -125,26 +155,57 @@ function startSyncServer() { // 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 { + const envVariables: Env = { + ...process.env, // required + ACTUAL_PORT: `${syncServerConfig.port}`, + ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, + ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, + ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, + }; + + if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { + // create directories if they do not exit - actual-sync doesn't do it for us... + mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); + } + + if (!fs.existsSync(syncServerConfig.ACTUAL_USER_FILES)) { + // create directories if they do not exit - actual-sync doesn't do it for us... + mkdir(syncServerConfig.ACTUAL_USER_FILES, { recursive: true }); + } + + // TODO: make sure .migrate file is also in user-directory under actual-server + + let forkOptions: ForkOptions = { + stdio: 'pipe', + env: envVariables, + }; + + if (isDev) { + forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; + } + + console.info('server-sync options', { forkOptions }); + 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' }, + forkOptions, ); + + 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'))})`); + }); } 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']) { diff --git a/yarn.lock b/yarn.lock index 79baabcfe72..42d02114764 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6254,7 +6254,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.0 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=bcb84c&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=384dfd&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.0" @@ -6274,7 +6274,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/eb84130ce8e62e8292658b12f48b5cd36d4647a37c22277842781dc1425f98475f47b24cb5c77c0c1695ee33b3ba05e842bacb7fb0ea511a8ee1f5f5a83760ac + checksum: 10/91dd1a3f845d020287f850a3a61882e65018f238d6159d6810900fdd991661b14afde96b762b8f9572aaa2907f0f1e19c98391dc4eb59d2a362adaf588f24dbd languageName: node linkType: hard @@ -8816,7 +8816,6 @@ __metadata: "@types/copyfiles": "npm:^2" "@types/fs-extra": "npm:^11" actual-sync: "file:../../../actual-server" - adm-zip: "npm:^0.5.10" better-sqlite3: "npm:^9.6.0" copyfiles: "npm:^2.4.1" cross-env: "npm:^7.0.3" From 2dc34de7277bac9395215fe3ece343cf1fc8f1c1 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Tue, 15 Oct 2024 17:15:32 +0100 Subject: [PATCH 18/33] package process --- bin/package-electron | 3 ++- packages/desktop-client/.gitignore | 1 + packages/desktop-client/vite.config.mts | 6 +++--- packages/desktop-electron/bin/update-client | 4 ++-- packages/desktop-electron/index.ts | 8 ++++++++ .../loot-core/webpack/webpack.desktop.config.js | 2 +- yarn.lock | 17 +++++------------ 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/bin/package-electron b/bin/package-electron index e3934ea84d0..f4cdf780bf8 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -36,7 +36,8 @@ fi yarn workspace loot-core build:node -yarn workspace @actual-app/web build --mode=desktop +yarn workspace @actual-app/web build:browser # required for web server (exposed via ngrok) +yarn workspace @actual-app/web build --mode=desktop # required for desktop app yarn workspace @actual-app/crdt build yarn workspace desktop-electron update-client diff --git a/packages/desktop-client/.gitignore b/packages/desktop-client/.gitignore index f635ae3caaa..d00b9758da4 100644 --- a/packages/desktop-client/.gitignore +++ b/packages/desktop-client/.gitignore @@ -10,6 +10,7 @@ playwright-report # production build +build-electron build-stats stats.json diff --git a/packages/desktop-client/vite.config.mts b/packages/desktop-client/vite.config.mts index ca7a93a61e7..44e986dfda0 100644 --- a/packages/desktop-client/vite.config.mts +++ b/packages/desktop-client/vite.config.mts @@ -15,7 +15,7 @@ const addWatchers = (): Plugin => ({ configureServer(server) { server.watcher .add([ - path.resolve('../loot-core/lib-dist/*.js'), + path.resolve('../loot-core/lib-dist/electron/*.js'), path.resolve('../loot-core/lib-dist/browser/*.js'), ]) .on('all', function () { @@ -109,7 +109,7 @@ export default defineConfig(async ({ mode }) => { build: { target: 'es2022', sourcemap: true, - outDir: 'build', + outDir: mode === 'desktop' ? 'build-electron' : 'build', assetsDir: 'static', manifest: true, assetsInlineLimit: 0, @@ -148,7 +148,7 @@ export default defineConfig(async ({ mode }) => { extensions: resolveExtensions, }, plugins: [ - // Macos electron (desktop) builds do not support PWA + // electron (desktop) builds do not support PWA mode === 'desktop' ? undefined : VitePWA({ diff --git a/packages/desktop-electron/bin/update-client b/packages/desktop-electron/bin/update-client index 24da05f6fcd..fa70190a32a 100755 --- a/packages/desktop-electron/bin/update-client +++ b/packages/desktop-electron/bin/update-client @@ -4,7 +4,7 @@ ROOT=`dirname $0`/.. rm -rf ${ROOT}/build mkdir -p ${ROOT}/build -cp -r ${ROOT}/../desktop-client/build ${ROOT}/build/client-build +cp -r ${ROOT}/../desktop-client/build-electron ${ROOT}/build/client-build # Remove the embedded backend for the browser version. Will improve # this process @@ -16,7 +16,7 @@ rm -rf ${ROOT}/build/client-build/*.map # Copy loot-core into build directory mkdir -p ${ROOT}/build/loot-core/lib-dist ls ${ROOT}/build/loot-core/lib-dist -cp ${ROOT}/../loot-core/lib-dist/bundle.desktop.js ${ROOT}/build/loot-core/lib-dist/bundle.desktop.js +cp ${ROOT}/../loot-core/lib-dist/electron/bundle.desktop.js ${ROOT}/build/loot-core/lib-dist/bundle.desktop.js cp ${ROOT}/../loot-core/default-db.sqlite ${ROOT}/build/loot-core/default-db.sqlite cp -r ${ROOT}/../loot-core/migrations ${ROOT}/build/loot-core/migrations diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 181115d05b9..89f39305c1d 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -152,6 +152,13 @@ function startSyncServer() { : '../node_modules/actual-sync/app.js', // Temporary - required because actual-server is in the other repo ); + const webRoot = path.resolve( + __dirname, + isDev + ? undefined // default is fine in dev + : '../node_modules/@actual-app/web/build/', // this the web build output + ); + // 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 { @@ -161,6 +168,7 @@ function startSyncServer() { ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, + ACTUAL_WEB_ROOT: webRoot, }; if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { diff --git a/packages/loot-core/webpack/webpack.desktop.config.js b/packages/loot-core/webpack/webpack.desktop.config.js index 50460f5597c..20c72c54a43 100644 --- a/packages/loot-core/webpack/webpack.desktop.config.js +++ b/packages/loot-core/webpack/webpack.desktop.config.js @@ -11,7 +11,7 @@ module.exports = { target: 'node', devtool: 'source-map', output: { - path: path.resolve(path.join(__dirname, '/../lib-dist')), + path: path.resolve(path.join(__dirname, '/../lib-dist/electron')), filename: 'bundle.desktop.js', sourceMapFilename: 'bundle.desktop.js.map', libraryTarget: 'commonjs2', diff --git a/yarn.lock b/yarn.lock index 42d02114764..4e88104d7ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,14 +55,7 @@ __metadata: languageName: unknown linkType: soft -"@actual-app/web@npm:24.10.0": - version: 24.10.0 - resolution: "@actual-app/web@npm:24.10.0" - checksum: 10/e713d98f69d7a8598f863c0fe559c2a87d56f0adc31e3bea62338bf9f9e8cfefc1fc41597e8ef0e3cdc1c51a611e330d023c9cde92e1d7b2b0a3f90e8a690254 - languageName: node - linkType: hard - -"@actual-app/web@workspace:packages/desktop-client": +"@actual-app/web@npm:24.10.1, @actual-app/web@workspace:packages/desktop-client": version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: @@ -6253,11 +6246,11 @@ __metadata: linkType: hard "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": - version: 24.10.0 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=384dfd&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + version: 24.10.1 + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=b4db45&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" - "@actual-app/web": "npm:24.10.0" + "@actual-app/web": "npm:24.10.1" bcrypt: "npm:^5.1.1" better-sqlite3: "npm:^9.6.0" body-parser: "npm:^1.20.3" @@ -6274,7 +6267,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/91dd1a3f845d020287f850a3a61882e65018f238d6159d6810900fdd991661b14afde96b762b8f9572aaa2907f0f1e19c98391dc4eb59d2a362adaf588f24dbd + checksum: 10/b8b9bab1dc179669a1ee628d2546cf27dab6cb216e7b161a06e06d255edcf92727187616dd05d9011c14f9fc8d605617fedd17352691ca0058a1c3f71c53cf8f languageName: node linkType: hard From 5d01674f6d5387765fa6e160af79921e9303e14f Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Tue, 15 Oct 2024 19:43:49 +0100 Subject: [PATCH 19/33] what an ordeal --- bin/package-electron | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/package-electron b/bin/package-electron index f4cdf780bf8..ac29060e879 100755 --- a/bin/package-electron +++ b/bin/package-electron @@ -34,11 +34,17 @@ if [ "$OSTYPE" == "msys" ]; then fi fi -yarn workspace loot-core build:node +# TODO: should do these async if possible -yarn workspace @actual-app/web build:browser # required for web server (exposed via ngrok) -yarn workspace @actual-app/web build --mode=desktop # required for desktop app +# required for when running in electron yarn workspace @actual-app/crdt build +yarn workspace loot-core build:node +yarn workspace @actual-app/web build --mode=desktop # electron specific build + +# required for when running in web server (exposed via ngrok) +yarn workspace loot-core build:browser +yarn workspace @actual-app/web build:browser + yarn workspace desktop-electron update-client From 8eca6437be7a641c50f7514bf77b07257b0b0598 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 17 Oct 2024 22:20:45 +0100 Subject: [PATCH 20/33] fixes --- packages/desktop-electron/bin/update-client | 5 ++--- packages/desktop-electron/index.ts | 20 +++++++++---------- packages/desktop-electron/package.json | 2 +- packages/loot-core/init-node.js | 2 +- .../src/platform/server/fs/index.electron.ts | 2 +- packages/loot-core/src/server/post.ts | 6 ++++++ yarn.lock | 4 ++-- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/desktop-electron/bin/update-client b/packages/desktop-electron/bin/update-client index fa70190a32a..d705152cc6c 100755 --- a/packages/desktop-electron/bin/update-client +++ b/packages/desktop-electron/bin/update-client @@ -14,9 +14,8 @@ rm -rf ${ROOT}/build/client-build/*.wasm rm -rf ${ROOT}/build/client-build/*.map # Copy loot-core into build directory -mkdir -p ${ROOT}/build/loot-core/lib-dist -ls ${ROOT}/build/loot-core/lib-dist -cp ${ROOT}/../loot-core/lib-dist/electron/bundle.desktop.js ${ROOT}/build/loot-core/lib-dist/bundle.desktop.js +mkdir -p ${ROOT}/build/loot-core/lib-dist/electron +cp ${ROOT}/../loot-core/lib-dist/electron/bundle.desktop.js ${ROOT}/build/loot-core/lib-dist/electron/bundle.desktop.js cp ${ROOT}/../loot-core/default-db.sqlite ${ROOT}/build/loot-core/default-db.sqlite cp -r ${ROOT}/../loot-core/migrations ${ROOT}/build/loot-core/migrations diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 89f39305c1d..fd18201245a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -35,8 +35,8 @@ import './security'; const isDev = !app.isPackaged; // dev mode if not packaged process.env.lootCoreScript = isDev - ? 'loot-core/lib-dist/bundle.desktop.js' // serve from local output in development (provides hot-reloading) - : path.resolve(__dirname, 'loot-core/lib-dist/bundle.desktop.js'); // serve from build in production + ? 'loot-core/lib-dist/electron/bundle.desktop.js' // serve from local output in development (provides hot-reloading) + : path.resolve(__dirname, 'loot-core/lib-dist/electron/bundle.desktop.js'); // serve from build in production // This allows relative URLs to be resolved to app:// which makes // local assets load correctly @@ -152,12 +152,12 @@ function startSyncServer() { : '../node_modules/actual-sync/app.js', // Temporary - required because actual-server is in the other repo ); - const webRoot = path.resolve( - __dirname, - isDev - ? undefined // default is fine in dev - : '../node_modules/@actual-app/web/build/', // this the web build output - ); + const webRoot = isDev + ? undefined + : path.resolve( + __dirname, + '../node_modules/@actual-app/web/build/', // this the web build output + ); // 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 @@ -192,8 +192,6 @@ function startSyncServer() { forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; } - console.info('server-sync options', { forkOptions }); - 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 [], @@ -378,7 +376,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - startSyncServer(); + // startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index bd98e77ba5e..b9a36df375b 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -21,7 +21,7 @@ "!**/*.js.map", "!**/stats.json", "!build/client-build/sql-wasm.wasm", - "!build/loot-core/lib-dist/{browser,bundle.mobile*}" + "!build/loot-core/lib-dist/electron/{browser,bundle.mobile*}" ], "beforePack": "./build/beforePackHook.js", "mac": { diff --git a/packages/loot-core/init-node.js b/packages/loot-core/init-node.js index 3470eccd0e3..13b4df0f5c4 100644 --- a/packages/loot-core/init-node.js +++ b/packages/loot-core/init-node.js @@ -4,7 +4,7 @@ import fetch from 'node-fetch'; import 'source-map-support/register'; // eslint-disable-next-line import/extensions -import bundle from './lib-dist/bundle.desktop.js'; +import bundle from './lib-dist/electron/bundle.desktop.js'; global.fetch = fetch; diff --git a/packages/loot-core/src/platform/server/fs/index.electron.ts b/packages/loot-core/src/platform/server/fs/index.electron.ts index 9d78de4bcb5..fc165ab6383 100644 --- a/packages/loot-core/src/platform/server/fs/index.electron.ts +++ b/packages/loot-core/src/platform/server/fs/index.electron.ts @@ -13,7 +13,7 @@ let rootPath = path.join(__dirname, '..', '..', '..', '..'); if (__filename.match('bundle')) { // The file name is not our filename and indicates that we're in the // bundled form. Because of this, the root path is different. - rootPath = path.join(__dirname, '..'); + rootPath = path.join(__dirname, '..', '..'); } export const init = () => { diff --git a/packages/loot-core/src/server/post.ts b/packages/loot-core/src/server/post.ts index 8b49c359d41..9761a397431 100644 --- a/packages/loot-core/src/server/post.ts +++ b/packages/loot-core/src/server/post.ts @@ -15,6 +15,12 @@ function throwIfNot200(res, text) { const json = JSON.parse(text); throw new PostError(json.reason); } + + if (res.headers.get('ngrok-error-code')) { + // When server is hosted behind ngrok and response code is not 200 we have experienced a network error + throw new PostError('network-failure'); + } + throw new PostError(text); } } diff --git a/yarn.lock b/yarn.lock index 50ce34384b0..d96859af24f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6388,7 +6388,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=b4db45&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=4569dd&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6408,7 +6408,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/b8b9bab1dc179669a1ee628d2546cf27dab6cb216e7b161a06e06d255edcf92727187616dd05d9011c14f9fc8d605617fedd17352691ca0058a1c3f71c53cf8f + checksum: 10/3c38b1628ec665ee4129184f8b44ebdc51ced8561d4b144578f500401b32faf49a590bf4303f3429ffeca179dcb4b67d37da7ddc4e1890f22f76a159b4976b20 languageName: node linkType: hard From b83a3c6e446221c75758969d5773cb196bd08fe5 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 17 Oct 2024 22:21:43 +0100 Subject: [PATCH 21/33] reenable sync server now tests have passed --- packages/desktop-electron/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index fd18201245a..9258bca2ce5 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -376,7 +376,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - // startSyncServer(); + startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } From 39d060bd0b824ec0caa057a7b9ee7e271a87d194 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Fri, 18 Oct 2024 21:42:33 +0100 Subject: [PATCH 22/33] splitting up server management to allow config of desktop app server and external serperately --- .../manager/ConfigExternalSyncServer.tsx | 273 ++++++++++++++++++ .../manager/ConfigInternalSyncServer.tsx | 40 +++ .../src/components/manager/ConfigServer.tsx | 258 ++--------------- .../src/components/manager/ManagementApp.jsx | 10 + packages/desktop-electron/index.ts | 19 +- 5 files changed, 357 insertions(+), 243 deletions(-) create mode 100644 packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx create mode 100644 packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx diff --git a/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx new file mode 100644 index 00000000000..dc3eed32a71 --- /dev/null +++ b/packages/desktop-client/src/components/manager/ConfigExternalSyncServer.tsx @@ -0,0 +1,273 @@ +// @ts-strict-ignore +import React, { useState, useEffect, useCallback } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + isNonProductionEnvironment, + isElectron, +} from 'loot-core/src/shared/environment'; + +import { useActions } from '../../hooks/useActions'; +import { useGlobalPref } from '../../hooks/useGlobalPref'; +import { useNavigate } from '../../hooks/useNavigate'; +import { theme } from '../../style'; +import { Button, ButtonWithLoading } from '../common/Button2'; +import { BigInput } from '../common/Input'; +import { Link } from '../common/Link'; +import { Text } from '../common/Text'; +import { View } from '../common/View'; +import { useServerURL, useSetServerURL } from '../ServerContext'; + +import { Title } from './subscribe/common'; + +export function ConfigExternalSyncServer() { + const { t } = useTranslation(); + const { createBudget, signOut, loggedIn } = useActions(); + const navigate = useNavigate(); + const [url, setUrl] = useState(''); + const currentUrl = useServerURL(); + const setServerUrl = useSetServerURL(); + useEffect(() => { + setUrl(currentUrl); + }, [currentUrl]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const restartElectronServer = useCallback(() => { + globalThis.window.Actual.restartElectronServer(); + setError(null); + }, []); + + const [_serverSelfSignedCert, setServerSelfSignedCert] = useGlobalPref( + 'serverSelfSignedCert', + restartElectronServer, + ); + + function getErrorMessage(error: string) { + switch (error) { + case 'network-failure': + return t( + 'Server is not running at this URL. Make sure you have HTTPS set up properly.', + ); + default: + return t( + 'Server does not look like an Actual server. Is it set up correctly?', + ); + } + } + + async function onSubmit() { + if (url === '' || loading) { + return; + } + + setError(null); + setLoading(true); + const { error } = await setServerUrl(url); + + if ( + ['network-failure', 'get-server-failure'].includes(error) && + !url.startsWith('http://') && + !url.startsWith('https://') + ) { + const { error } = await setServerUrl('https://' + url); + if (error) { + setUrl('https://' + url); + setError(error); + } else { + await signOut(); + navigate('/'); + } + setLoading(false); + } else if (error) { + setLoading(false); + setError(error); + } else { + setLoading(false); + await signOut(); + navigate('/'); + } + } + + function onSameDomain() { + setUrl(window.location.origin); + } + + async function onSelectSelfSignedCertificate() { + const selfSignedCertificateLocation = await window.Actual?.openFileDialog({ + properties: ['openFile'], + filters: [ + { + name: 'Self Signed Certificate', + extensions: ['crt', 'pem'], + }, + ], + }); + + if (selfSignedCertificateLocation) { + setServerSelfSignedCert(selfSignedCertificateLocation[0]); + } + } + + async function onSkip() { + await setServerUrl(null); + await loggedIn(); + navigate('/'); + } + + async function onCreateTestFile() { + await setServerUrl(null); + await createBudget({ testMode: true }); + window.__navigate('/'); + } + + if (isElectron()) { + } + + return ( + + + + <Text + style={{ + fontSize: 16, + color: theme.tableRowHeaderText, + lineHeight: 1.5, + }} + > + {currentUrl ? ( + <Trans> + Existing sessions will be logged out and you will log in to this + server. We will validate that Actual is running at this URL. + </Trans> + ) : ( + <Trans> + There is no server configured. After running the server, specify the + URL here to use the app. You can always change this later. We will + validate that Actual is running at this URL. + </Trans> + )} + </Text> + + {error && ( + <> + <Text + style={{ + marginTop: 20, + color: theme.errorText, + borderRadius: 4, + fontSize: 15, + }} + > + {getErrorMessage(error)} + </Text> + {isElectron() && ( + <View + style={{ display: 'flex', flexDirection: 'row', marginTop: 20 }} + > + <Text + style={{ + color: theme.errorText, + borderRadius: 4, + fontSize: 15, + }} + > + <Trans> + If the server is using a self-signed certificate{' '} + <Link + variant="text" + style={{ fontSize: 15 }} + onClick={onSelectSelfSignedCertificate} + > + select it here + </Link> + . + </Trans> + </Text> + </View> + )} + </> + )} + + <View style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }}> + <BigInput + autoFocus={true} + placeholder={t('https://example.com')} + value={url || ''} + onChangeValue={setUrl} + style={{ flex: 1, marginRight: 10 }} + onEnter={onSubmit} + /> + <ButtonWithLoading + variant="primary" + isLoading={loading} + style={{ fontSize: 15 }} + onPress={onSubmit} + > + {t('OK')} + </ButtonWithLoading> + {currentUrl && ( + <Button + variant="bare" + style={{ fontSize: 15, marginLeft: 10 }} + onPress={() => navigate(-1)} + > + {t('Cancel')} + </Button> + )} + </View> + + <View + style={{ + flexDirection: 'row', + flexFlow: 'row wrap', + justifyContent: 'center', + marginTop: 15, + }} + > + {currentUrl ? ( + <Button + variant="bare" + style={{ color: theme.pageTextLight }} + onPress={onSkip} + > + {t('Stop using a server')} + </Button> + ) : ( + <> + {!isElectron() && ( + <Button + variant="bare" + style={{ + color: theme.pageTextLight, + margin: 5, + marginRight: 15, + }} + onPress={onSameDomain} + > + {t('Use current domain')} + </Button> + )} + <Button + variant="bare" + style={{ color: theme.pageTextLight, margin: 5 }} + onPress={onSkip} + > + {t('Don’t use a server')} + </Button> + + {isNonProductionEnvironment() && ( + <Button + variant="primary" + style={{ marginLeft: 15 }} + onPress={onCreateTestFile} + > + {t('Create test file')} + </Button> + )} + </> + )} + </View> + </View> + ); +} diff --git a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx new file mode 100644 index 00000000000..07595c0cf4a --- /dev/null +++ b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx @@ -0,0 +1,40 @@ +// @ts-strict-ignore +import React, { useState, useEffect, useCallback } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { useNavigate } from '../../hooks/useNavigate'; +import { theme } from '../../style'; +import { Button, ButtonWithLoading } from '../common/Button2'; +import { Text } from '../common/Text'; +import { View } from '../common/View'; +import { useServerURL, useSetServerURL } from '../ServerContext'; + +import { Title } from './subscribe/common'; + +export function ConfigInternalSyncServer() { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( + <View style={{ maxWidth: 500, marginTop: -30 }}> + <Title text={t('Server configuration')} /> + + <Text + style={{ + fontSize: 16, + color: theme.pageText, + lineHeight: 1.5, + }} + > + <Trans> + Actual can setup a server for you to sync your data across devices. It + can either run on your computer or on a server. If you want to run it + on your computer, you can use the button below to start the server. If + you want to run it on a server, you can enter the URL of the server + below. + </Trans> + </Text> + <Button onPress={() => navigate(-1)}>Back</Button> + </View> + ); +} diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index d8c1727e042..7712b39af5f 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -1,270 +1,60 @@ // @ts-strict-ignore -import React, { useState, useEffect, useCallback } from 'react'; +import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { - isNonProductionEnvironment, - isElectron, -} from 'loot-core/src/shared/environment'; +import { isElectron } from 'loot-core/src/shared/environment'; -import { useActions } from '../../hooks/useActions'; -import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; import { theme } from '../../style'; -import { Button, ButtonWithLoading } from '../common/Button2'; -import { BigInput } from '../common/Input'; -import { Link } from '../common/Link'; +import { Button } from '../common/Button2'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { useServerURL, useSetServerURL } from '../ServerContext'; import { Title } from './subscribe/common'; export function ConfigServer() { const { t } = useTranslation(); - const { createBudget, signOut, loggedIn } = useActions(); const navigate = useNavigate(); - const [url, setUrl] = useState(''); - const currentUrl = useServerURL(); - const setServerUrl = useSetServerURL(); - useEffect(() => { - setUrl(currentUrl); - }, [currentUrl]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState<string | null>(null); - - const restartElectronServer = useCallback(() => { - globalThis.window.Actual.restartElectronServer(); - setError(null); - }, []); - - const [_serverSelfSignedCert, setServerSelfSignedCert] = useGlobalPref( - 'serverSelfSignedCert', - restartElectronServer, - ); - - function getErrorMessage(error: string) { - switch (error) { - case 'network-failure': - return t( - 'Server is not running at this URL. Make sure you have HTTPS set up properly.', - ); - default: - return t( - 'Server does not look like an Actual server. Is it set up correctly?', - ); - } - } - - async function onSubmit() { - if (url === '' || loading) { - return; - } - - setError(null); - setLoading(true); - const { error } = await setServerUrl(url); - - if ( - ['network-failure', 'get-server-failure'].includes(error) && - !url.startsWith('http://') && - !url.startsWith('https://') - ) { - const { error } = await setServerUrl('https://' + url); - if (error) { - setUrl('https://' + url); - setError(error); - } else { - await signOut(); - navigate('/'); - } - setLoading(false); - } else if (error) { - setLoading(false); - setError(error); - } else { - setLoading(false); - await signOut(); - navigate('/'); - } - } - - function onSameDomain() { - setUrl(window.location.origin); - } - - async function onSelectSelfSignedCertificate() { - const selfSignedCertificateLocation = await window.Actual?.openFileDialog({ - properties: ['openFile'], - filters: [ - { - name: 'Self Signed Certificate', - extensions: ['crt', 'pem'], - }, - ], - }); - - if (selfSignedCertificateLocation) { - setServerSelfSignedCert(selfSignedCertificateLocation[0]); - } - } - - async function onSkip() { - await setServerUrl(null); - await loggedIn(); - navigate('/'); - } - - async function onCreateTestFile() { - await setServerUrl(null); - await createBudget({ testMode: true }); - window.__navigate('/'); - } return ( <View style={{ maxWidth: 500, marginTop: -30 }}> - <Title text={t('Where’s the server?')} /> + <Title text={t('Let’s set up your server!')} /> <Text style={{ fontSize: 16, - color: theme.tableRowHeaderText, + color: theme.pageText, lineHeight: 1.5, }} > - {currentUrl ? ( - <Trans> - Existing sessions will be logged out and you will log in to this - server. We will validate that Actual is running at this URL. - </Trans> - ) : ( - <Trans> - There is no server configured. After running the server, specify the - URL here to use the app. You can always change this later. We will - validate that Actual is running at this URL. - </Trans> - )} + <Trans> + If you like, Actual can setup a server for you to sync your data + across devices. + </Trans> </Text> - - {error && ( + {isElectron() && ( <> <Text style={{ - marginTop: 20, - color: theme.errorText, - borderRadius: 4, - fontSize: 15, + fontSize: 16, + color: theme.pageText, + lineHeight: 1.5, }} > - {getErrorMessage(error)} + <Trans> + Would you like to host the server on your computer or connect to + an external server? + </Trans> </Text> - {isElectron() && ( - <View - style={{ display: 'flex', flexDirection: 'row', marginTop: 20 }} - > - <Text - style={{ - color: theme.errorText, - borderRadius: 4, - fontSize: 15, - }} - > - <Trans> - If the server is using a self-signed certificate{' '} - <Link - variant="text" - style={{ fontSize: 15 }} - onClick={onSelectSelfSignedCertificate} - > - select it here - </Link> - . - </Trans> - </Text> - </View> - )} - </> - )} - - <View style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }}> - <BigInput - autoFocus={true} - placeholder={t('https://example.com')} - value={url || ''} - onChangeValue={setUrl} - style={{ flex: 1, marginRight: 10 }} - onEnter={onSubmit} - /> - <ButtonWithLoading - variant="primary" - isLoading={loading} - style={{ fontSize: 15 }} - onPress={onSubmit} - > - {t('OK')} - </ButtonWithLoading> - {currentUrl && ( - <Button - variant="bare" - style={{ fontSize: 15, marginLeft: 10 }} - onPress={() => navigate(-1)} - > - {t('Cancel')} + <Button onPress={() => navigate('/config-server/internal')}> + Host on this computer </Button> - )} - </View> - - <View - style={{ - flexDirection: 'row', - flexFlow: 'row wrap', - justifyContent: 'center', - marginTop: 15, - }} - > - {currentUrl ? ( - <Button - variant="bare" - style={{ color: theme.pageTextLight }} - onPress={onSkip} - > - {t('Stop using a server')} + <Button onPress={() => navigate('/config-server/external')}> + Connect to an external server </Button> - ) : ( - <> - {!isElectron() && ( - <Button - variant="bare" - style={{ - color: theme.pageTextLight, - margin: 5, - marginRight: 15, - }} - onPress={onSameDomain} - > - {t('Use current domain')} - </Button> - )} - <Button - variant="bare" - style={{ color: theme.pageTextLight, margin: 5 }} - onPress={onSkip} - > - {t('Don’t use a server')} - </Button> - - {isNonProductionEnvironment() && ( - <Button - variant="primary" - style={{ marginLeft: 15 }} - onPress={onCreateTestFile} - > - {t('Create test file')} - </Button> - )} - </> - )} - </View> + </> + )} + <Button onPress={() => navigate('/')}>I don’t want a server</Button> </View> ); } diff --git a/packages/desktop-client/src/components/manager/ManagementApp.jsx b/packages/desktop-client/src/components/manager/ManagementApp.jsx index b9550088c6c..6e6a64d96c5 100644 --- a/packages/desktop-client/src/components/manager/ManagementApp.jsx +++ b/packages/desktop-client/src/components/manager/ManagementApp.jsx @@ -20,6 +20,8 @@ import { Notifications } from '../Notifications'; import { useServerVersion } from '../ServerContext'; import { BudgetList } from './BudgetList'; +import { ConfigExternalSyncServer } from './ConfigExternalSyncServer'; +import { ConfigInternalSyncServer } from './ConfigInternalSyncServer'; import { ConfigServer } from './ConfigServer'; import { ServerURL } from './ServerURL'; import { Bootstrap } from './subscribe/Bootstrap'; @@ -126,6 +128,14 @@ export function ManagementApp() { <> <Routes> <Route path="/config-server" element={<ConfigServer />} /> + <Route + path="/config-server/external" + element={<ConfigExternalSyncServer />} + /> + <Route + path="/config-server/internal" + element={<ConfigInternalSyncServer />} + /> <Route path="/change-password" element={<ChangePassword />} /> {files && files.length > 0 ? ( diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 9258bca2ce5..dfb4374d874 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -152,25 +152,26 @@ function startSyncServer() { : '../node_modules/actual-sync/app.js', // Temporary - required because actual-server is in the other repo ); - const webRoot = isDev - ? undefined - : path.resolve( - __dirname, - '../node_modules/@actual-app/web/build/', // this the web build output - ); - // 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 { - const envVariables: Env = { + let envVariables: Env = { ...process.env, // required ACTUAL_PORT: `${syncServerConfig.port}`, ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, - ACTUAL_WEB_ROOT: webRoot, }; + if (!isDev) { + const webRoot = path.resolve( + __dirname, + '../node_modules/@actual-app/web/build/', // this the web build output + ); + + envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; + } + if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { // create directories if they do not exit - actual-sync doesn't do it for us... mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); From 45d9fae206a86e610517ec607d87cb47d8824d5c Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 20 Oct 2024 10:30:36 +0100 Subject: [PATCH 23/33] fix the port thing --- package.json | 2 +- packages/desktop-electron/index.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index cc58c2d61f9..36628540203 100644 --- a/package.json +++ b/package.json @@ -21,7 +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-server-dependencies": "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", diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index dfb4374d874..6721a6c25dd 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -118,7 +118,7 @@ function createBackgroundProcess() { function startSyncServer() { const syncServerConfig = { - port: 5006, // actual-server seems to only run on 5006 - I can't get it to work on anything else... + port: 5007, ACTUAL_SERVER_DATA_DIR: path.resolve( process.env.ACTUAL_DATA_DIR, 'actual-server', @@ -163,14 +163,14 @@ function startSyncServer() { ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, }; - if (!isDev) { - const webRoot = path.resolve( - __dirname, - '../node_modules/@actual-app/web/build/', // this the web build output - ); + const webRoot = path.resolve( + __dirname, + isDev + ? '../../../node_modules/@actual-app/web/build/' // workspace node_modules + : '../node_modules/@actual-app/web/build/', // location of packaged module + ); - envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; - } + envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { // create directories if they do not exit - actual-sync doesn't do it for us... From 0a333ed3967c14a6c523bf56ae9eb2a4a10aa6b1 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 20 Oct 2024 12:35:58 +0100 Subject: [PATCH 24/33] tunnel erorrs cleanup --- packages/desktop-electron/index.ts | 2 +- packages/loot-core/src/server/post.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 6721a6c25dd..bd5101851d7 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -377,7 +377,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - startSyncServer(); + // startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } diff --git a/packages/loot-core/src/server/post.ts b/packages/loot-core/src/server/post.ts index 9761a397431..50d4e1679c3 100644 --- a/packages/loot-core/src/server/post.ts +++ b/packages/loot-core/src/server/post.ts @@ -16,8 +16,17 @@ function throwIfNot200(res, text) { throw new PostError(json.reason); } - if (res.headers.get('ngrok-error-code')) { - // When server is hosted behind ngrok and response code is not 200 we have experienced a network error + // Actual Sync Server may be exposed via a tunnel (e.g. ngrok). Tunnel errors should be treated as network errors. + const tunnelErrorHeaders = ['ngrok-error-code']; + + const headerKeys = res.headers.keys().toArray(); + const tunnelError = tunnelErrorHeaders.some(tunnelErrorHeader => + headerKeys.includes(tunnelErrorHeader), + ); + + if (tunnelError) { + // Tunnel errors are present when the tunnel is active and the server is not reachable e.g. server is offline + // When we experience a tunnel error we treat it as a network failure throw new PostError('network-failure'); } From a78ad8824b7a742d791c259888636ec1e9f9ed0e Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 20 Oct 2024 15:30:21 +0100 Subject: [PATCH 25/33] trickle in --- packages/desktop-electron/index.ts | 2 +- packages/loot-core/src/server/post.ts | 6 ++---- yarn.lock | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index bd5101851d7..6721a6c25dd 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -377,7 +377,7 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - // startSyncServer(); + startSyncServer(); exposeSyncServer(globalPrefs.ngrokConfig); } diff --git a/packages/loot-core/src/server/post.ts b/packages/loot-core/src/server/post.ts index 50d4e1679c3..1fa28cd22a0 100644 --- a/packages/loot-core/src/server/post.ts +++ b/packages/loot-core/src/server/post.ts @@ -18,10 +18,8 @@ function throwIfNot200(res, text) { // Actual Sync Server may be exposed via a tunnel (e.g. ngrok). Tunnel errors should be treated as network errors. const tunnelErrorHeaders = ['ngrok-error-code']; - - const headerKeys = res.headers.keys().toArray(); - const tunnelError = tunnelErrorHeaders.some(tunnelErrorHeader => - headerKeys.includes(tunnelErrorHeader), + const tunnelError = tunnelErrorHeaders.some(header => + res.headers.has(header), ); if (tunnelError) { diff --git a/yarn.lock b/yarn.lock index d96859af24f..73bc00d88f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6388,7 +6388,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=4569dd&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=462a94&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6408,7 +6408,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/3c38b1628ec665ee4129184f8b44ebdc51ced8561d4b144578f500401b32faf49a590bf4303f3429ffeca179dcb4b67d37da7ddc4e1890f22f76a159b4976b20 + checksum: 10/6106458ead5b8b7e2a948fee74a9b46633ebfd8096af1cc2c3e3946ab9ff571d60018bbf805948b4611a0f5cd93da5ca216290ec3a8ef68a367a44fe9cb8d429 languageName: node linkType: hard From 804425fd06879d19c594ca9760e61194263e606b Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 21 Oct 2024 19:14:22 +0100 Subject: [PATCH 26/33] resetting state if not available to avoid hard crashes --- packages/desktop-electron/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 6721a6c25dd..44bbc65f65a 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -73,7 +73,8 @@ async function loadGlobalPrefs() { ), ); } catch (e) { - console.log('Could not load global state'); + console.error('Could not load global state - resetting', e); + state = {}; } return state; From a1de454f602475ed41bb321441e2563e388fbe3a Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 21 Oct 2024 21:15:28 +0100 Subject: [PATCH 27/33] small updates until I figure out what the issue is --- .../src/components/manager/ConfigServer.tsx | 12 +++++++++++- packages/desktop-electron/index.ts | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index 7712b39af5f..5754929156d 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -4,17 +4,27 @@ import { Trans, useTranslation } from 'react-i18next'; import { isElectron } from 'loot-core/src/shared/environment'; +import { useActions } from '../../hooks/useActions'; import { useNavigate } from '../../hooks/useNavigate'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Text } from '../common/Text'; import { View } from '../common/View'; +import { useSetServerURL } from '../ServerContext'; import { Title } from './subscribe/common'; export function ConfigServer() { const { t } = useTranslation(); const navigate = useNavigate(); + const setServerUrl = useSetServerURL(); + const { loggedIn } = useActions(); + + async function onSkip() { + await setServerUrl(null); + await loggedIn(); + navigate('/'); + } return ( <View style={{ maxWidth: 500, marginTop: -30 }}> @@ -54,7 +64,7 @@ export function ConfigServer() { </Button> </> )} - <Button onPress={() => navigate('/')}>I don’t want a server</Button> + <Button onPress={onSkip}>I don’t want a server</Button> </View> ); } diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 44bbc65f65a..eec6214d012 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -73,7 +73,7 @@ async function loadGlobalPrefs() { ), ); } catch (e) { - console.error('Could not load global state - resetting', e); + console.info('Could not load global state - using defaults'); // This could be the first time running the app - no global-store.json state = {}; } From 6d0363f7c743d0510f16a75483f0513290f1d4e8 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 27 Oct 2024 18:25:58 +0000 Subject: [PATCH 28/33] settings --- .../src/components/manager/BudgetList.tsx | 42 ----- .../manager/ConfigInternalSyncServer.tsx | 146 +++++++++++++++--- yarn.lock | 4 +- 3 files changed, 130 insertions(+), 62 deletions(-) diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index eb335c3f652..1cd0f5e3c96 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -23,7 +23,6 @@ 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'; @@ -442,27 +441,6 @@ 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 hasRequiredNgrokSettings = - ngrokConfig?.authToken && ngrokConfig?.port && ngrokConfig?.domain; - if (hasRequiredNgrokSettings) { - const url = await globalThis.Actual.exposeActualServer({ - authToken: ngrokConfig.authToken, - port: ngrokConfig.port, - domain: ngrokConfig.domain, - }); - - console.info('exposing actual at: ' + url); - } else { - console.info('ngrok settings not set'); - } - }; - return ( <View style={{ @@ -537,26 +515,6 @@ 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> diff --git a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx index 07595c0cf4a..7e0be3ddf98 100644 --- a/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigInternalSyncServer.tsx @@ -1,40 +1,150 @@ // @ts-strict-ignore import React, { useState, useEffect, useCallback } from 'react'; +import { Group, NumberField } from 'react-aria-components'; import { Trans, useTranslation } from 'react-i18next'; +import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; -import { theme } from '../../style'; +import { useResponsive } from '../../ResponsiveProvider'; +import { styles, theme } from '../../style'; import { Button, ButtonWithLoading } from '../common/Button2'; +import { Input } from '../common/Input'; +import { Label } from '../common/Label'; import { Text } from '../common/Text'; import { View } from '../common/View'; -import { useServerURL, useSetServerURL } from '../ServerContext'; import { Title } from './subscribe/common'; export function ConfigInternalSyncServer() { const { t } = useTranslation(); const navigate = useNavigate(); + const [serverConfig, setServerConfig] = useState({ + port: 5007, + certificatePath: undefined, // merge with current global.json pref + ngrokDomain: undefined, + ngrokAuthToken: undefined, + }); + + const startActualServer = async () => { + await globalThis.Actual.startActualServer('v24.10.1'); + }; + + const { isNarrowWidth } = useResponsive(); + const narrowButtonStyle = isNarrowWidth + ? { + height: styles.mobileMinHeight, + } + : {}; + + const [ngrokConfig] = useGlobalPref('ngrokConfig'); + const exposeActualServer = async () => { + const hasRequiredNgrokSettings = + ngrokConfig?.authToken && ngrokConfig?.port && ngrokConfig?.domain; + if (hasRequiredNgrokSettings) { + const url = await globalThis.Actual.exposeActualServer({ + authToken: ngrokConfig.authToken, + port: ngrokConfig.port, + domain: ngrokConfig.domain, + }); + + console.info('exposing actual at: ' + url); + } else { + console.info('ngrok settings not set'); + } + }; + + const handleChange = (name: keyof typeof serverConfig, event) => { + console.info(event); + const { value } = event.target; + setServerConfig({ + ...serverConfig, + [name]: value, + }); + }; return ( <View style={{ maxWidth: 500, marginTop: -30 }}> <Title text={t('Server configuration')} /> - <Text - style={{ - fontSize: 16, - color: theme.pageText, - lineHeight: 1.5, - }} - > - <Trans> - Actual can setup a server for you to sync your data across devices. It - can either run on your computer or on a server. If you want to run it - on your computer, you can use the button below to start the server. If - you want to run it on a server, you can enter the URL of the server - below. - </Trans> - </Text> - <Button onPress={() => navigate(-1)}>Back</Button> + <View> + <Text + style={{ + fontSize: 16, + color: theme.pageText, + lineHeight: 1.5, + }} + > + <Trans> + Actual can setup a server for you to sync your data across devices. + It can either run on your computer or on a server. If you want to + run it on your computer, you can use the button below to start the + server. If you want to run it on a server, you can enter the URL of + the server below. + </Trans> + </Text> + + <View> + <Label title={t('Port')} /> + <Input + type="number" + value={serverConfig.port} + name={t('Port')} + onChange={event => handleChange('port', event)} + /> + </View> + + <View> + <Label title={t('SSL Certificate (optional)')} /> + <Input + type="file" + value={serverConfig.certificatePath} + name={t('Certificate')} + onChange={event => handleChange('certificatePath', event)} + /> + </View> + + <View> + <Label title={t('Ngrok custom domain (optional)')} /> + <Input + type="text" + value={serverConfig.ngrokDomain} + name={t('Ngrok Custom Domain')} + onChange={event => handleChange('ngrokDomain', event)} + /> + </View> + <View> + <Label title={t('Ngrok auth token (optional)')} /> + <Input + type="text" + value={serverConfig.ngrokAuthToken} + name={t('Ngrok Auth Token')} + onChange={event => handleChange('ngrokAuthToken', event)} + /> + </View> + </View> + <View> + <Button onPress={() => navigate(-1)}>Back</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> ); } diff --git a/yarn.lock b/yarn.lock index aa5c12d80ba..9ec2d694a7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6603,7 +6603,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=d86144&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=fefd95&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6623,7 +6623,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/ad0fd9e40428f3914ccc4ebed056c0f6cfe77676b53becea09d6e528a6c0dce564cd4f6c1324a441f426545853a0a0140ef3a3583606a50ba2cc1427dc679834 + checksum: 10/ed0b8268d4433477f654900b0e78a0f95eccd286c7abbce88f5d6865a9f16c3377dc5811d52d67745fdbf5ad4c84ece443d73008017e6713258d4fbca024bad4 languageName: node linkType: hard From f3c2743ce3583b3c7a3492ee253f5b1627fb5a3d Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 27 Oct 2024 20:28:54 +0000 Subject: [PATCH 29/33] remove it --- packages/desktop-electron/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index eec6214d012..3b8abd93e21 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -229,7 +229,6 @@ async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) { 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"), From 76b2918ae65bb6909976d059d7373bb9df966ca9 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Sun, 27 Oct 2024 22:53:08 +0000 Subject: [PATCH 30/33] thoughts --- packages/desktop-electron/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 3b8abd93e21..01b40bb1235 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -173,6 +173,15 @@ function startSyncServer() { envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; + // const customDomain = globalPrefs?.ngrokConfig?.domain; + + // if (customDomain) { + // // If we expose on a custom domain via ngrok we need to tell the server to allow it to work as a proxy + // // I'm not sure about this. It needs a CIDR block. I'm not sure what to put here or if it is needed. + // // It's possible this setting will prevent the annoying auth issue where I have to login every day + // envVariables = { ...envVariables, ACTUAL_TRUSTED_PROXIES: customDomain }; + // } + if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { // create directories if they do not exit - actual-sync doesn't do it for us... mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); From 9ff112c31ee6b22197d8589dc4291c694aa69b6e Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Mon, 28 Oct 2024 16:47:35 +0000 Subject: [PATCH 31/33] waiting on sync server before starting --- packages/desktop-electron/index.ts | 111 +++++++++++++++++------------ 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 01b40bb1235..4acb096eff3 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -155,54 +155,64 @@ function startSyncServer() { // 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 { - let envVariables: Env = { - ...process.env, // required - ACTUAL_PORT: `${syncServerConfig.port}`, - ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, - ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, - ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, - }; - - const webRoot = path.resolve( - __dirname, - isDev - ? '../../../node_modules/@actual-app/web/build/' // workspace node_modules - : '../node_modules/@actual-app/web/build/', // location of packaged module - ); + let envVariables: Env = { + ...process.env, // required + ACTUAL_PORT: `${syncServerConfig.port}`, + ACTUAL_SERVER_FILES: `${syncServerConfig.ACTUAL_SERVER_FILES}`, + ACTUAL_USER_FILES: `${syncServerConfig.ACTUAL_USER_FILES}`, + ACTUAL_DATA_DIR: `${syncServerConfig.ACTUAL_SERVER_DATA_DIR}`, + }; - envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; + const webRoot = path.resolve( + __dirname, + isDev + ? '../../../node_modules/@actual-app/web/build/' // workspace node_modules + : '../node_modules/@actual-app/web/build/', // location of packaged module + ); - // const customDomain = globalPrefs?.ngrokConfig?.domain; + envVariables = { ...envVariables, ACTUAL_WEB_ROOT: webRoot }; - // if (customDomain) { - // // If we expose on a custom domain via ngrok we need to tell the server to allow it to work as a proxy - // // I'm not sure about this. It needs a CIDR block. I'm not sure what to put here or if it is needed. - // // It's possible this setting will prevent the annoying auth issue where I have to login every day - // envVariables = { ...envVariables, ACTUAL_TRUSTED_PROXIES: customDomain }; - // } + // const customDomain = globalPrefs?.ngrokConfig?.domain; - if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { - // create directories if they do not exit - actual-sync doesn't do it for us... - mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); - } + // if (customDomain) { + // // If we expose on a custom domain via ngrok we need to tell the server to allow it to work as a proxy + // // I'm not sure about this. It needs a CIDR block. I'm not sure what to put here or if it is needed. + // // It's possible this setting will prevent the annoying auth issue where I have to login every day + // envVariables = { ...envVariables, ACTUAL_TRUSTED_PROXIES: customDomain }; + // } - if (!fs.existsSync(syncServerConfig.ACTUAL_USER_FILES)) { - // create directories if they do not exit - actual-sync doesn't do it for us... - mkdir(syncServerConfig.ACTUAL_USER_FILES, { recursive: true }); - } + if (!fs.existsSync(syncServerConfig.ACTUAL_SERVER_FILES)) { + // create directories if they do not exit - actual-sync doesn't do it for us... + mkdir(syncServerConfig.ACTUAL_SERVER_FILES, { recursive: true }); + } - // TODO: make sure .migrate file is also in user-directory under actual-server + if (!fs.existsSync(syncServerConfig.ACTUAL_USER_FILES)) { + // create directories if they do not exit - actual-sync doesn't do it for us... + mkdir(syncServerConfig.ACTUAL_USER_FILES, { recursive: true }); + } - let forkOptions: ForkOptions = { - stdio: 'pipe', - env: envVariables, - }; + // TODO: make sure .migrate file is also in user-directory under actual-server - if (isDev) { - forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; - } + let forkOptions: ForkOptions = { + stdio: 'pipe', + env: envVariables, + }; + if (isDev) { + forkOptions = { ...forkOptions, execArgv: ['--inspect'] }; + } + + const SYNC_SERVER_WAIT_TIMEOUT = 10000; // wait 10 seconds for the server to start - if it doesn't, throw an error + + const syncServerTimeout = new Promise<void>((_, reject) => { + setTimeout(() => { + const errorMessage = `Sync server failed to start within ${SYNC_SERVER_WAIT_TIMEOUT / 1000} seconds. Something is wrong. Please raise a github issue.`; + console.error(errorMessage); + reject(new Error(errorMessage)); + }, SYNC_SERVER_WAIT_TIMEOUT); + }); + + const syncServerPromise = new Promise<void>(async resolve => { 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 [], @@ -211,18 +221,24 @@ function startSyncServer() { actualServerProcess.stdout?.on('data', (chunk: Buffer) => { // Send the Server console.log messages to the main browser window + const chunkValue = JSON.stringify(chunk.toString('utf8')); + if (chunkValue.includes('Listening on')) { + console.info('Actual Sync Server has started!'); + resolve(); // The server is running - resolve + } + clientWin?.webContents.executeJavaScript(` - console.info('Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); + console.info('Actual Sync Server Log:', ${chunkValue})`); }); 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'))})`); + console.error('Actual Sync Server Log:', ${JSON.stringify(chunk.toString('utf8'))})`); }); - } catch (error) { - console.error(error); - } + }); + + return Promise.race([syncServerPromise, syncServerTimeout]); // Either the server has started or the timeout is reached } async function exposeSyncServer(ngrokConfig: GlobalPrefs['ngrokConfig']) { @@ -386,8 +402,11 @@ app.on('ready', async () => { globalPrefs = await loadGlobalPrefs(); // load global prefs if (globalPrefs.ngrokConfig?.autoStart) { - startSyncServer(); - exposeSyncServer(globalPrefs.ngrokConfig); + // wait for both server and ngrok to start before starting the Actual client to ensure server is available + await Promise.allSettled([ + startSyncServer(), + exposeSyncServer(globalPrefs.ngrokConfig), + ]); } protocol.handle('app', request => { From 312e85e3dd52f5d750eaa34e83c40d0e77560ac3 Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Wed, 30 Oct 2024 11:35:12 +0000 Subject: [PATCH 32/33] fix timeout message --- packages/desktop-electron/index.ts | 20 +++++++++------ packages/loot-core/init-node.js | 39 ------------------------------ 2 files changed, 13 insertions(+), 46 deletions(-) delete mode 100644 packages/loot-core/init-node.js diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 4acb096eff3..84ef85fd40e 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -204,13 +204,7 @@ function startSyncServer() { const SYNC_SERVER_WAIT_TIMEOUT = 10000; // wait 10 seconds for the server to start - if it doesn't, throw an error - const syncServerTimeout = new Promise<void>((_, reject) => { - setTimeout(() => { - const errorMessage = `Sync server failed to start within ${SYNC_SERVER_WAIT_TIMEOUT / 1000} seconds. Something is wrong. Please raise a github issue.`; - console.error(errorMessage); - reject(new Error(errorMessage)); - }, SYNC_SERVER_WAIT_TIMEOUT); - }); + let syncServerStarted = false; const syncServerPromise = new Promise<void>(async resolve => { actualServerProcess = utilityProcess.fork( @@ -224,6 +218,7 @@ function startSyncServer() { const chunkValue = JSON.stringify(chunk.toString('utf8')); if (chunkValue.includes('Listening on')) { console.info('Actual Sync Server has started!'); + syncServerStarted = true; resolve(); // The server is running - resolve } @@ -238,6 +233,17 @@ function startSyncServer() { }); }); + const syncServerTimeout = new Promise<void>((_, reject) => { + setTimeout(() => { + if (!syncServerStarted) { + const errorMessage = `Sync server failed to start within ${SYNC_SERVER_WAIT_TIMEOUT / 1000} seconds. Something is wrong. Please raise a github issue.`; + console.error(errorMessage); + reject(new Error(errorMessage)); + } + }, SYNC_SERVER_WAIT_TIMEOUT); + }); + + // This aint working... return Promise.race([syncServerPromise, syncServerTimeout]); // Either the server has started or the timeout is reached } diff --git a/packages/loot-core/init-node.js b/packages/loot-core/init-node.js deleted file mode 100644 index 13b4df0f5c4..00000000000 --- a/packages/loot-core/init-node.js +++ /dev/null @@ -1,39 +0,0 @@ -import { dirname, basename } from 'path'; - -import fetch from 'node-fetch'; -import 'source-map-support/register'; - -// eslint-disable-next-line import/extensions -import bundle from './lib-dist/electron/bundle.desktop.js'; - -global.fetch = fetch; - -async function init(budgetPath) { - const dir = dirname(budgetPath); - const budgetId = basename(budgetPath); - await bundle.initEmbedded('0.0.147', true, dir); - await bundle.lib.send('load-budget', { id: budgetId }); - - return bundle.lib; -} - -async function run() { - const { send } = await init('/tmp/_test-budget'); - const accounts = await send('accounts-get'); - - await send('transaction-add', { - date: '2022-03-20', - account: accounts[0].id, - amount: 1000, - }); - - await new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 5000); - }); - - await send('close-budget'); -} - -run(); From 4f667d07eb4766b6b456f7f3c083adec4af244dc Mon Sep 17 00:00:00 2001 From: Mike Clark <mclarkgb@gmail.com> Date: Wed, 30 Oct 2024 14:02:51 +0000 Subject: [PATCH 33/33] note --- packages/desktop-electron/index.ts | 3 ++- yarn.lock | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 84ef85fd40e..402ecbe4f0d 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -217,9 +217,10 @@ function startSyncServer() { // Send the Server console.log messages to the main browser window const chunkValue = JSON.stringify(chunk.toString('utf8')); if (chunkValue.includes('Listening on')) { + // can we send a signal from the server instead of doing this? console.info('Actual Sync Server has started!'); syncServerStarted = true; - resolve(); // The server is running - resolve + resolve(); } clientWin?.webContents.executeJavaScript(` diff --git a/yarn.lock b/yarn.lock index 9ec2d694a7e..4a6706e2fc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6603,7 +6603,7 @@ __metadata: "actual-sync@file:../../../actual-server::locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron": version: 24.10.1 - resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=fefd95&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" + resolution: "actual-sync@file:../../../actual-server#../../../actual-server::hash=f2a23f&locator=desktop-electron%40workspace%3Apackages%2Fdesktop-electron" dependencies: "@actual-app/crdt": "npm:2.1.0" "@actual-app/web": "npm:24.10.1" @@ -6623,7 +6623,7 @@ __metadata: nordigen-node: "npm:^1.4.0" uuid: "npm:^9.0.0" winston: "npm:^3.14.2" - checksum: 10/ed0b8268d4433477f654900b0e78a0f95eccd286c7abbce88f5d6865a9f16c3377dc5811d52d67745fdbf5ad4c84ece443d73008017e6713258d4fbca024bad4 + checksum: 10/c546a8d3b4f83e511558f676c9735e39827c4440de6cf49cadc05072b324d9f36c938c4134bebbf256297e79c78b355225e23890f22482f4d2153e6496c469fe languageName: node linkType: hard