From af506f40edb1c9c339cc86baf40baccf2dc6da62 Mon Sep 17 00:00:00 2001 From: vantezzen Date: Fri, 18 Oct 2019 20:53:41 +0200 Subject: Develop local server feature --- src/api/apiBase.js | 6 +++++ src/config.js | 2 ++ src/electron/ipc-api/index.js | 2 ++ src/electron/ipc-api/localServer.js | 52 +++++++++++++++++++++++++++++++++++++ src/helpers/serverless-helpers.js | 4 ++- src/i18n/locales/zh-HANT.json | 6 +++++ src/index.js | 3 --- src/server/config/database.js | 6 +---- src/server/start.js | 31 +++++++++++----------- src/stores/RequestStore.js | 9 +++++++ src/stores/SettingsStore.js | 14 +++++++++- 11 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 src/electron/ipc-api/localServer.js diff --git a/src/api/apiBase.js b/src/api/apiBase.js index e8d571171..561b025f0 100644 --- a/src/api/apiBase.js +++ b/src/api/apiBase.js @@ -4,6 +4,9 @@ import { API_VERSION, } from '../environment'; +import { + LOCAL_SERVER, +} from '../config'; const apiBase = () => { let url; @@ -21,6 +24,9 @@ const apiBase = () => { // on some routes. This would result in Ferdi deleting its current authToken as it thinks it // has gone invalid. url = 'https://1.1.1.1'; + } else if (window.ferdi.stores.settings.all.app.server === LOCAL_SERVER) { + // Use URL for local server + url = `http://127.0.0.1:${window.ferdi.stores.requests.localServerPort}`; } else { // Load URL from store url = window.ferdi.stores.settings.all.app.server; diff --git a/src/config.js b/src/config.js index 0673e994a..e30a6d4b2 100644 --- a/src/config.js +++ b/src/config.js @@ -111,6 +111,8 @@ export const FILE_SYSTEM_SETTINGS_TYPES = [ 'proxy', ]; +export const LOCAL_SERVER = 'You are using Ferdi without a server'; + export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config'); // Replacing app.asar is not beautiful but unforunately necessary diff --git a/src/electron/ipc-api/index.js b/src/electron/ipc-api/index.js index 3b7f31e4b..dcdef6b32 100644 --- a/src/electron/ipc-api/index.js +++ b/src/electron/ipc-api/index.js @@ -3,6 +3,7 @@ import settings from './settings'; import appIndicator from './appIndicator'; import download from './download'; import processManager from './processManager'; +import localServer from './localServer'; export default (params) => { settings(params); @@ -10,4 +11,5 @@ export default (params) => { appIndicator(params); download(params); processManager(params); + localServer(params); }; diff --git a/src/electron/ipc-api/localServer.js b/src/electron/ipc-api/localServer.js new file mode 100644 index 000000000..2f8f1020a --- /dev/null +++ b/src/electron/ipc-api/localServer.js @@ -0,0 +1,52 @@ +import { ipcMain, app } from 'electron'; +import path from 'path'; +import net from 'net'; +import startServer from '../../server/start'; + +const DEFAULT_PORT = 45569; + +const portInUse = function (port) { + return new Promise((resolve) => { + const server = net.createServer((socket) => { + socket.write('Echo server\r\n'); + socket.pipe(socket); + }); + + server.listen(port, '127.0.0.1'); + server.on('error', () => { + resolve(true); + }); + server.on('listening', () => { + server.close(); + resolve(false); + }); + }); +}; + +let localServerStarted = false; + +export default (params) => { + ipcMain.on('startLocalServer', () => { + if (!localServerStarted) { + // Find next unused port for server + let port = DEFAULT_PORT; + (async () => { + // eslint-disable-next-line no-await-in-loop + while (await portInUse(port) && port < DEFAULT_PORT + 10) { + port += 1; + } + console.log('Starting local server on port', port); + + startServer( + path.join(app.getPath('userData'), 'server.sqlite'), + port, + ); + + params.mainWindow.webContents.send('localServerPort', { + port, + }); + })(); + localServerStarted = true; + } + }); +}; diff --git a/src/helpers/serverless-helpers.js b/src/helpers/serverless-helpers.js index 741bce7f9..01549e038 100644 --- a/src/helpers/serverless-helpers.js +++ b/src/helpers/serverless-helpers.js @@ -1,9 +1,11 @@ +import { LOCAL_SERVER } from '../config'; + export default function useLocalServer(actions) { // Use local server for user actions.settings.update({ type: 'app', data: { - server: 'http://localhost:45569', + server: LOCAL_SERVER, }, }); diff --git a/src/i18n/locales/zh-HANT.json b/src/i18n/locales/zh-HANT.json index 5784c1ab6..678554c05 100644 --- a/src/i18n/locales/zh-HANT.json +++ b/src/i18n/locales/zh-HANT.json @@ -11,6 +11,7 @@ "feature.delayApp.upgrade.actionShort": "Upgrade account", "feature.quickSwitch.info": "Select a service with TAB, ↑ and ↓. Open a service with ENTER.", "feature.quickSwitch.search": "Search...", + "feature.quickSwitch.title": "QuickSwitch", "feature.serviceLimit.limitReached": "You have added {amount} out of {limit} services that are included in your plan. Please upgrade your account to add more services.", "feature.shareFranz.action.email": "Send as email", "feature.shareFranz.action.facebook": "Share on Facebook", @@ -213,11 +214,13 @@ "settings.account.upgradeToPro.label": "Upgrade to Ferdi Professional", "settings.account.userInfoRequestFailed": "無法載入帳戶資訊", "settings.account.yourLicense": "Your Ferdi License", + "settings.app.accentColorInfo": "Write your accent color in a CSS-compatible format. (Default: #7367f0)", "settings.app.buttonClearAllCache": "Clear cache", "settings.app.buttonInstallUpdate": "重新啟動並且更新", "settings.app.buttonSearchForUpdate": "Check for updates", "settings.app.cacheInfo": "Ferdi cache is currently using {size} of disk space.", "settings.app.currentVersion": "當前版本:", + "settings.app.form.accentColor": "Accent color", "settings.app.form.autoLaunchInBackground": "背景啟動", "settings.app.form.autoLaunchOnStart": "開機時啟動", "settings.app.form.beta": "包含開發中版本", @@ -244,6 +247,7 @@ "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled", "settings.app.form.showServiceNavigationBar": "Always show service navigation bar", "settings.app.form.todoServer": "Todo Server", + "settings.app.form.universalDarkMode": "Enable universal Dark Mode", "settings.app.headline": "Settings", "settings.app.headlineAdvanced": "Advanced", "settings.app.headlineAppearance": "Appearance", @@ -263,6 +267,7 @@ "settings.app.subheadlineCache": "Cache", "settings.app.todoServerInfo": "This server will be used for the \"Ferdi Todo\" feature. (default: https://app.franztodos.com)", "settings.app.translationHelp": "Help us to translate Ferdi into your language.", + "settings.app.universalDarkModeInfo": "Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.", "settings.app.updateStatusAvailable": "有可用更新,下載中...", "settings.app.updateStatusSearching": "檢查更新中...", "settings.app.updateStatusUpToDate": "已經是最新版本了", @@ -315,6 +320,7 @@ "settings.service.form.indirectMessages": "針對全部訊息顯示通知", "settings.service.form.isMutedInfo": "When disabled, all notification sounds and audio playback are muted", "settings.service.form.name": "名子", + "settings.service.form.openDarkmodeCss": "Open darkmode.css", "settings.service.form.proxy.headline": "HTTP/HTTPS Proxy Settings", "settings.service.form.proxy.host": "Proxy Host/IP", "settings.service.form.proxy.info": "Proxy settings will not synced with the Ferdi servers.", diff --git a/src/index.js b/src/index.js index 7a0e89285..4d7215d5e 100644 --- a/src/index.js +++ b/src/index.js @@ -34,9 +34,6 @@ import { isPositionValid } from './electron/windowUtils'; import { appId } from './package.json'; // eslint-disable-line import/no-unresolved import './electron/exception'; -// Start internal server -import './server/start'; - import { DEFAULT_APP_SETTINGS, DEFAULT_WINDOW_OPTIONS, diff --git a/src/server/config/database.js b/src/server/config/database.js index 86f18dac5..a413f7050 100644 --- a/src/server/config/database.js +++ b/src/server/config/database.js @@ -2,11 +2,7 @@ /** @type {import('@adonisjs/framework/src/Env')} */ const Env = use('Env'); -// eslint-disable-next-line import/no-extraneous-dependencies -const { app } = require('electron'); -const path = require('path'); - -const dbPath = path.join(app.getPath('userData'), 'server.sqlite'); +const dbPath = process.env.DB_PATH; module.exports = { /* diff --git a/src/server/start.js b/src/server/start.js index 8a8711a78..34b2cb5fa 100644 --- a/src/server/start.js +++ b/src/server/start.js @@ -17,24 +17,25 @@ */ const path = require('path'); const fs = require('fs-extra'); -// eslint-disable-next-line import/no-extraneous-dependencies -const { app } = require('electron'); process.env.ENV_PATH = path.join(__dirname, 'env.ini'); -// Make sure local database exists -const dbPath = path.join(app.getPath('userData'), 'server.sqlite'); -if (!fs.existsSync(dbPath)) { - fs.copySync( - path.join(__dirname, 'database', 'template.sqlite'), - dbPath, - ); -} - const { Ignitor } = require('@adonisjs/ignitor'); const fold = require('@adonisjs/fold'); -new Ignitor(fold) - .appRoot(__dirname) - .fireHttpServer() - .catch(console.error); // eslint-disable-line no-console +module.exports = (dbPath, port) => { + if (!fs.existsSync(dbPath)) { + fs.copySync( + path.join(__dirname, 'database', 'template.sqlite'), + dbPath, + ); + } + + process.env.DB_PATH = dbPath; + process.env.PORT = port; + + new Ignitor(fold) + .appRoot(__dirname) + .fireHttpServer() + .catch(console.error); // eslint-disable-line no-console +}; diff --git a/src/stores/RequestStore.js b/src/stores/RequestStore.js index 2587d4eef..a92f4c685 100644 --- a/src/stores/RequestStore.js +++ b/src/stores/RequestStore.js @@ -1,3 +1,4 @@ +import { ipcRenderer } from 'electron'; import { action, computed, observable } from 'mobx'; import ms from 'ms'; @@ -12,6 +13,8 @@ export default class RequestStore extends Store { @observable showRequiredRequestsError = false; + @observable localServerPort = 45569; + retries = 0; retryDelay = ms('2s'); @@ -29,6 +32,12 @@ export default class RequestStore extends Store { setup() { this.userInfoRequest = this.stores.user.getUserInfoRequest; this.servicesRequest = this.stores.services.allServicesRequest; + + ipcRenderer.on('localServerPort', (event, data) => { + if (data.port) { + this.localServerPort = data.port; + } + }); } @computed get areRequiredRequestsSuccessful() { diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js index 8c4cd47eb..df0fc77e9 100644 --- a/src/stores/SettingsStore.js +++ b/src/stores/SettingsStore.js @@ -9,7 +9,7 @@ import Request from './lib/Request'; import { getLocale } from '../helpers/i18n-helpers'; import { API } from '../environment'; -import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES } from '../config'; +import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES, LOCAL_SERVER } from '../config'; import { SPELLCHECKER_LOCALES } from '../i18n/languages'; const debug = require('debug')('Ferdi:SettingsStore'); @@ -52,6 +52,18 @@ export default class SettingsStore extends Store { ), ); + reaction( + () => this.all.app.server, + (server) => { + if (server === LOCAL_SERVER) { + ipcRenderer.send('startLocalServer'); + } + }, + { + fireImmediately: true, + }, + ); + reaction( () => this.all.app.locked, () => { -- cgit v1.2.3-70-g09d2