aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/api/apiBase.js6
-rw-r--r--src/config.js2
-rw-r--r--src/electron/ipc-api/index.js2
-rw-r--r--src/electron/ipc-api/localServer.js52
-rw-r--r--src/helpers/serverless-helpers.js4
-rw-r--r--src/i18n/locales/zh-HANT.json6
-rw-r--r--src/index.js3
-rw-r--r--src/server/config/database.js6
-rw-r--r--src/server/start.js31
-rw-r--r--src/stores/RequestStore.js9
-rw-r--r--src/stores/SettingsStore.js14
11 files changed, 110 insertions, 25 deletions
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 @@
4import { 4import {
5 API_VERSION, 5 API_VERSION,
6} from '../environment'; 6} from '../environment';
7import {
8 LOCAL_SERVER,
9} from '../config';
7 10
8const apiBase = () => { 11const apiBase = () => {
9 let url; 12 let url;
@@ -21,6 +24,9 @@ const apiBase = () => {
21 // on some routes. This would result in Ferdi deleting its current authToken as it thinks it 24 // on some routes. This would result in Ferdi deleting its current authToken as it thinks it
22 // has gone invalid. 25 // has gone invalid.
23 url = 'https://1.1.1.1'; 26 url = 'https://1.1.1.1';
27 } else if (window.ferdi.stores.settings.all.app.server === LOCAL_SERVER) {
28 // Use URL for local server
29 url = `http://127.0.0.1:${window.ferdi.stores.requests.localServerPort}`;
24 } else { 30 } else {
25 // Load URL from store 31 // Load URL from store
26 url = window.ferdi.stores.settings.all.app.server; 32 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 = [
111 'proxy', 111 'proxy',
112]; 112];
113 113
114export const LOCAL_SERVER = 'You are using Ferdi without a server';
115
114export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config'); 116export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config');
115 117
116// Replacing app.asar is not beautiful but unforunately necessary 118// 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';
3import appIndicator from './appIndicator'; 3import appIndicator from './appIndicator';
4import download from './download'; 4import download from './download';
5import processManager from './processManager'; 5import processManager from './processManager';
6import localServer from './localServer';
6 7
7export default (params) => { 8export default (params) => {
8 settings(params); 9 settings(params);
@@ -10,4 +11,5 @@ export default (params) => {
10 appIndicator(params); 11 appIndicator(params);
11 download(params); 12 download(params);
12 processManager(params); 13 processManager(params);
14 localServer(params);
13}; 15};
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 @@
1import { ipcMain, app } from 'electron';
2import path from 'path';
3import net from 'net';
4import startServer from '../../server/start';
5
6const DEFAULT_PORT = 45569;
7
8const portInUse = function (port) {
9 return new Promise((resolve) => {
10 const server = net.createServer((socket) => {
11 socket.write('Echo server\r\n');
12 socket.pipe(socket);
13 });
14
15 server.listen(port, '127.0.0.1');
16 server.on('error', () => {
17 resolve(true);
18 });
19 server.on('listening', () => {
20 server.close();
21 resolve(false);
22 });
23 });
24};
25
26let localServerStarted = false;
27
28export default (params) => {
29 ipcMain.on('startLocalServer', () => {
30 if (!localServerStarted) {
31 // Find next unused port for server
32 let port = DEFAULT_PORT;
33 (async () => {
34 // eslint-disable-next-line no-await-in-loop
35 while (await portInUse(port) && port < DEFAULT_PORT + 10) {
36 port += 1;
37 }
38 console.log('Starting local server on port', port);
39
40 startServer(
41 path.join(app.getPath('userData'), 'server.sqlite'),
42 port,
43 );
44
45 params.mainWindow.webContents.send('localServerPort', {
46 port,
47 });
48 })();
49 localServerStarted = true;
50 }
51 });
52};
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 @@
1import { LOCAL_SERVER } from '../config';
2
1export default function useLocalServer(actions) { 3export default function useLocalServer(actions) {
2 // Use local server for user 4 // Use local server for user
3 actions.settings.update({ 5 actions.settings.update({
4 type: 'app', 6 type: 'app',
5 data: { 7 data: {
6 server: 'http://localhost:45569', 8 server: LOCAL_SERVER,
7 }, 9 },
8 }); 10 });
9 11
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 @@
11 "feature.delayApp.upgrade.actionShort": "Upgrade account", 11 "feature.delayApp.upgrade.actionShort": "Upgrade account",
12 "feature.quickSwitch.info": "Select a service with TAB, ↑ and ↓. Open a service with ENTER.", 12 "feature.quickSwitch.info": "Select a service with TAB, ↑ and ↓. Open a service with ENTER.",
13 "feature.quickSwitch.search": "Search...", 13 "feature.quickSwitch.search": "Search...",
14 "feature.quickSwitch.title": "QuickSwitch",
14 "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.", 15 "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.",
15 "feature.shareFranz.action.email": "Send as email", 16 "feature.shareFranz.action.email": "Send as email",
16 "feature.shareFranz.action.facebook": "Share on Facebook", 17 "feature.shareFranz.action.facebook": "Share on Facebook",
@@ -213,11 +214,13 @@
213 "settings.account.upgradeToPro.label": "Upgrade to Ferdi Professional", 214 "settings.account.upgradeToPro.label": "Upgrade to Ferdi Professional",
214 "settings.account.userInfoRequestFailed": "無法載入帳戶資訊", 215 "settings.account.userInfoRequestFailed": "無法載入帳戶資訊",
215 "settings.account.yourLicense": "Your Ferdi License", 216 "settings.account.yourLicense": "Your Ferdi License",
217 "settings.app.accentColorInfo": "Write your accent color in a CSS-compatible format. (Default: #7367f0)",
216 "settings.app.buttonClearAllCache": "Clear cache", 218 "settings.app.buttonClearAllCache": "Clear cache",
217 "settings.app.buttonInstallUpdate": "重新啟動並且更新", 219 "settings.app.buttonInstallUpdate": "重新啟動並且更新",
218 "settings.app.buttonSearchForUpdate": "Check for updates", 220 "settings.app.buttonSearchForUpdate": "Check for updates",
219 "settings.app.cacheInfo": "Ferdi cache is currently using {size} of disk space.", 221 "settings.app.cacheInfo": "Ferdi cache is currently using {size} of disk space.",
220 "settings.app.currentVersion": "當前版本:", 222 "settings.app.currentVersion": "當前版本:",
223 "settings.app.form.accentColor": "Accent color",
221 "settings.app.form.autoLaunchInBackground": "背景啟動", 224 "settings.app.form.autoLaunchInBackground": "背景啟動",
222 "settings.app.form.autoLaunchOnStart": "開機時啟動", 225 "settings.app.form.autoLaunchOnStart": "開機時啟動",
223 "settings.app.form.beta": "包含開發中版本", 226 "settings.app.form.beta": "包含開發中版本",
@@ -244,6 +247,7 @@
244 "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled", 247 "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled",
245 "settings.app.form.showServiceNavigationBar": "Always show service navigation bar", 248 "settings.app.form.showServiceNavigationBar": "Always show service navigation bar",
246 "settings.app.form.todoServer": "Todo Server", 249 "settings.app.form.todoServer": "Todo Server",
250 "settings.app.form.universalDarkMode": "Enable universal Dark Mode",
247 "settings.app.headline": "Settings", 251 "settings.app.headline": "Settings",
248 "settings.app.headlineAdvanced": "Advanced", 252 "settings.app.headlineAdvanced": "Advanced",
249 "settings.app.headlineAppearance": "Appearance", 253 "settings.app.headlineAppearance": "Appearance",
@@ -263,6 +267,7 @@
263 "settings.app.subheadlineCache": "Cache", 267 "settings.app.subheadlineCache": "Cache",
264 "settings.app.todoServerInfo": "This server will be used for the \"Ferdi Todo\" feature. (default: https://app.franztodos.com)", 268 "settings.app.todoServerInfo": "This server will be used for the \"Ferdi Todo\" feature. (default: https://app.franztodos.com)",
265 "settings.app.translationHelp": "Help us to translate Ferdi into your language.", 269 "settings.app.translationHelp": "Help us to translate Ferdi into your language.",
270 "settings.app.universalDarkModeInfo": "Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.",
266 "settings.app.updateStatusAvailable": "有可用更新,下載中...", 271 "settings.app.updateStatusAvailable": "有可用更新,下載中...",
267 "settings.app.updateStatusSearching": "檢查更新中...", 272 "settings.app.updateStatusSearching": "檢查更新中...",
268 "settings.app.updateStatusUpToDate": "已經是最新版本了", 273 "settings.app.updateStatusUpToDate": "已經是最新版本了",
@@ -315,6 +320,7 @@
315 "settings.service.form.indirectMessages": "針對全部訊息顯示通知", 320 "settings.service.form.indirectMessages": "針對全部訊息顯示通知",
316 "settings.service.form.isMutedInfo": "When disabled, all notification sounds and audio playback are muted", 321 "settings.service.form.isMutedInfo": "When disabled, all notification sounds and audio playback are muted",
317 "settings.service.form.name": "名子", 322 "settings.service.form.name": "名子",
323 "settings.service.form.openDarkmodeCss": "Open darkmode.css",
318 "settings.service.form.proxy.headline": "HTTP/HTTPS Proxy Settings", 324 "settings.service.form.proxy.headline": "HTTP/HTTPS Proxy Settings",
319 "settings.service.form.proxy.host": "Proxy Host/IP", 325 "settings.service.form.proxy.host": "Proxy Host/IP",
320 "settings.service.form.proxy.info": "Proxy settings will not synced with the Ferdi servers.", 326 "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';
34import { appId } from './package.json'; // eslint-disable-line import/no-unresolved 34import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
35import './electron/exception'; 35import './electron/exception';
36 36
37// Start internal server
38import './server/start';
39
40import { 37import {
41 DEFAULT_APP_SETTINGS, 38 DEFAULT_APP_SETTINGS,
42 DEFAULT_WINDOW_OPTIONS, 39 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 @@
2/** @type {import('@adonisjs/framework/src/Env')} */ 2/** @type {import('@adonisjs/framework/src/Env')} */
3const Env = use('Env'); 3const Env = use('Env');
4 4
5// eslint-disable-next-line import/no-extraneous-dependencies 5const dbPath = process.env.DB_PATH;
6const { app } = require('electron');
7const path = require('path');
8
9const dbPath = path.join(app.getPath('userData'), 'server.sqlite');
10 6
11module.exports = { 7module.exports = {
12 /* 8 /*
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 @@
17*/ 17*/
18const path = require('path'); 18const path = require('path');
19const fs = require('fs-extra'); 19const fs = require('fs-extra');
20// eslint-disable-next-line import/no-extraneous-dependencies
21const { app } = require('electron');
22 20
23process.env.ENV_PATH = path.join(__dirname, 'env.ini'); 21process.env.ENV_PATH = path.join(__dirname, 'env.ini');
24 22
25// Make sure local database exists
26const dbPath = path.join(app.getPath('userData'), 'server.sqlite');
27if (!fs.existsSync(dbPath)) {
28 fs.copySync(
29 path.join(__dirname, 'database', 'template.sqlite'),
30 dbPath,
31 );
32}
33
34const { Ignitor } = require('@adonisjs/ignitor'); 23const { Ignitor } = require('@adonisjs/ignitor');
35const fold = require('@adonisjs/fold'); 24const fold = require('@adonisjs/fold');
36 25
37new Ignitor(fold) 26module.exports = (dbPath, port) => {
38 .appRoot(__dirname) 27 if (!fs.existsSync(dbPath)) {
39 .fireHttpServer() 28 fs.copySync(
40 .catch(console.error); // eslint-disable-line no-console 29 path.join(__dirname, 'database', 'template.sqlite'),
30 dbPath,
31 );
32 }
33
34 process.env.DB_PATH = dbPath;
35 process.env.PORT = port;
36
37 new Ignitor(fold)
38 .appRoot(__dirname)
39 .fireHttpServer()
40 .catch(console.error); // eslint-disable-line no-console
41};
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 @@
1import { ipcRenderer } from 'electron';
1import { action, computed, observable } from 'mobx'; 2import { action, computed, observable } from 'mobx';
2import ms from 'ms'; 3import ms from 'ms';
3 4
@@ -12,6 +13,8 @@ export default class RequestStore extends Store {
12 13
13 @observable showRequiredRequestsError = false; 14 @observable showRequiredRequestsError = false;
14 15
16 @observable localServerPort = 45569;
17
15 retries = 0; 18 retries = 0;
16 19
17 retryDelay = ms('2s'); 20 retryDelay = ms('2s');
@@ -29,6 +32,12 @@ export default class RequestStore extends Store {
29 setup() { 32 setup() {
30 this.userInfoRequest = this.stores.user.getUserInfoRequest; 33 this.userInfoRequest = this.stores.user.getUserInfoRequest;
31 this.servicesRequest = this.stores.services.allServicesRequest; 34 this.servicesRequest = this.stores.services.allServicesRequest;
35
36 ipcRenderer.on('localServerPort', (event, data) => {
37 if (data.port) {
38 this.localServerPort = data.port;
39 }
40 });
32 } 41 }
33 42
34 @computed get areRequiredRequestsSuccessful() { 43 @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';
9import { getLocale } from '../helpers/i18n-helpers'; 9import { getLocale } from '../helpers/i18n-helpers';
10import { API } from '../environment'; 10import { API } from '../environment';
11 11
12import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES } from '../config'; 12import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES, LOCAL_SERVER } from '../config';
13import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 13import { SPELLCHECKER_LOCALES } from '../i18n/languages';
14 14
15const debug = require('debug')('Ferdi:SettingsStore'); 15const debug = require('debug')('Ferdi:SettingsStore');
@@ -53,6 +53,18 @@ export default class SettingsStore extends Store {
53 ); 53 );
54 54
55 reaction( 55 reaction(
56 () => this.all.app.server,
57 (server) => {
58 if (server === LOCAL_SERVER) {
59 ipcRenderer.send('startLocalServer');
60 }
61 },
62 {
63 fireImmediately: true,
64 },
65 );
66
67 reaction(
56 () => this.all.app.locked, 68 () => this.all.app.locked,
57 () => { 69 () => {
58 const { router } = window.ferdi.stores; 70 const { router } = window.ferdi.stores;