aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml1
-rw-r--r--electron-builder.yml1
-rw-r--r--package-lock.json148
-rw-r--r--package.json9
-rw-r--r--packages/theme/package-lock.json2
-rw-r--r--packages/theme/src/themes/dark/index.ts2
-rw-r--r--packages/theme/src/themes/default/index.ts3
-rw-r--r--src/components/services/content/ConnectionLostBanner.js119
-rw-r--r--src/components/services/content/ServiceView.js8
-rw-r--r--src/components/services/tabs/TabItem.js2
-rw-r--r--src/components/settings/services/EditServiceForm.js14
-rw-r--r--src/components/ui/FeatureList.js5
-rw-r--r--src/containers/settings/EditServiceScreen.js17
-rw-r--r--src/electron/macOSPermissions.js14
-rw-r--r--src/features/announcements/components/AnnouncementScreen.js41
-rw-r--r--src/features/planSelection/components/PlanItem.js19
-rw-r--r--src/features/planSelection/components/PlanSelection.js76
-rw-r--r--src/features/planSelection/containers/PlanSelectionScreen.js4
-rw-r--r--src/helpers/plan-helpers.js12
-rw-r--r--src/helpers/userAgent-helpers.js45
-rw-r--r--src/i18n/locales/defaultMessages.json447
-rw-r--r--src/i18n/locales/en-US.json8
-rw-r--r--src/i18n/messages/src/components/services/content/ConnectionBanner.json67
-rw-r--r--src/i18n/messages/src/components/services/content/ConnectionLost.json67
-rw-r--r--src/i18n/messages/src/components/services/content/ConnectionLostBanner.json41
-rw-r--r--src/i18n/messages/src/components/services/content/WebControls.json67
-rw-r--r--src/i18n/messages/src/components/settings/services/EditServiceForm.json45
-rw-r--r--src/i18n/messages/src/components/ui/FeatureList.json13
-rw-r--r--src/i18n/messages/src/containers/settings/EditServiceScreen.json65
-rw-r--r--src/i18n/messages/src/containers/settings/EditSettingsScreen.json2
-rw-r--r--src/i18n/messages/src/features/recipeConnectionLost/components/WebControls.json67
-rw-r--r--src/index.js13
m---------src/internal-server0
-rw-r--r--src/models/Recipe.js4
-rw-r--r--src/models/Service.js54
-rw-r--r--src/stores/AppStore.js15
-rw-r--r--src/stores/ServicesStore.js67
-rw-r--r--src/webview/lib/RecipeWebview.js4
39 files changed, 1409 insertions, 180 deletions
diff --git a/.gitignore b/.gitignore
index d38c475bf..626cd7ea2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ yarn-error.log
12npm-debug.log* 12npm-debug.log*
13lerna-debug.log 13lerna-debug.log
14uidev/lib 14uidev/lib
15*.tsbuildinfo \ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 29bdd5ba8..e0fd7b8ae 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -27,6 +27,7 @@ before_script:
27 - travis_retry npm install node-sass -g 27 - travis_retry npm install node-sass -g
28script: 28script:
29 - travis_retry travis_wait 100 npm run build 29 - travis_retry travis_wait 100 npm run build
30
30cache: npm 31cache: npm
31 32
32branches: 33branches:
diff --git a/electron-builder.yml b/electron-builder.yml
index 23625c825..330fc64e1 100644
--- a/electron-builder.yml
+++ b/electron-builder.yml
@@ -60,4 +60,5 @@ protocols:
60 60
61asarUnpack: 61asarUnpack:
62 - ./recipes 62 - ./recipes
63 - ./node_modules/mac-screen-capture-permissions
63 - ./assets/images/taskbar 64 - ./assets/images/taskbar
diff --git a/package-lock.json b/package-lock.json
index 2200d61d1..b1aa4e88d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10525,6 +10525,15 @@
10525 } 10525 }
10526 } 10526 }
10527 }, 10527 },
10528 "electron-util": {
10529 "version": "0.14.0",
10530 "resolved": "https://registry.npmjs.org/electron-util/-/electron-util-0.14.0.tgz",
10531 "integrity": "sha512-f8DXlOLrI7aq2S1yRlgx8Dc+Zbq5hlKKyVUnc73HyLEijo8/0KT9Cqlcduy75qwwynVw+cvbuqtqgIlrHLijyw==",
10532 "requires": {
10533 "electron-is-dev": "^1.1.0",
10534 "new-github-issue-url": "^0.2.1"
10535 }
10536 },
10528 "electron-window-state": { 10537 "electron-window-state": {
10529 "version": "5.0.3", 10538 "version": "5.0.3",
10530 "resolved": "https://registry.npmjs.org/electron-window-state/-/electron-window-state-5.0.3.tgz", 10539 "resolved": "https://registry.npmjs.org/electron-window-state/-/electron-window-state-5.0.3.tgz",
@@ -18541,6 +18550,118 @@
18541 "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", 18550 "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz",
18542 "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" 18551 "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0="
18543 }, 18552 },
18553 "mac-screen-capture-permissions": {
18554 "version": "1.1.0",
18555 "resolved": "https://registry.npmjs.org/mac-screen-capture-permissions/-/mac-screen-capture-permissions-1.1.0.tgz",
18556 "integrity": "sha512-jMRumlB3FScui/7yW+5FqqbuO7CQ0XOJVT5oTsb7W9eRQDhCIpJpIF0XxLVXwq2DIOp0fYsz1LFiBjnyDYULyQ==",
18557 "requires": {
18558 "electron-util": "^0.13.0",
18559 "execa": "^2.0.4",
18560 "macos-version": "^5.2.0"
18561 },
18562 "dependencies": {
18563 "cross-spawn": {
18564 "version": "7.0.2",
18565 "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz",
18566 "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==",
18567 "requires": {
18568 "path-key": "^3.1.0",
18569 "shebang-command": "^2.0.0",
18570 "which": "^2.0.1"
18571 }
18572 },
18573 "electron-util": {
18574 "version": "0.13.1",
18575 "resolved": "https://registry.npmjs.org/electron-util/-/electron-util-0.13.1.tgz",
18576 "integrity": "sha512-CvOuAyQPaPtnDp7SspwnT1yTb1yynw6yp4LrZCfEJ7TG/kJFiZW9RqMHlCEFWMn3QNoMkNhGVeCvWJV5NsYyuQ==",
18577 "requires": {
18578 "electron-is-dev": "^1.1.0",
18579 "new-github-issue-url": "^0.2.1"
18580 }
18581 },
18582 "execa": {
18583 "version": "2.1.0",
18584 "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz",
18585 "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==",
18586 "requires": {
18587 "cross-spawn": "^7.0.0",
18588 "get-stream": "^5.0.0",
18589 "is-stream": "^2.0.0",
18590 "merge-stream": "^2.0.0",
18591 "npm-run-path": "^3.0.0",
18592 "onetime": "^5.1.0",
18593 "p-finally": "^2.0.0",
18594 "signal-exit": "^3.0.2",
18595 "strip-final-newline": "^2.0.0"
18596 }
18597 },
18598 "get-stream": {
18599 "version": "5.1.0",
18600 "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
18601 "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
18602 "requires": {
18603 "pump": "^3.0.0"
18604 }
18605 },
18606 "is-stream": {
18607 "version": "2.0.0",
18608 "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
18609 "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
18610 },
18611 "merge-stream": {
18612 "version": "2.0.0",
18613 "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
18614 "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
18615 },
18616 "npm-run-path": {
18617 "version": "3.1.0",
18618 "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz",
18619 "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==",
18620 "requires": {
18621 "path-key": "^3.0.0"
18622 }
18623 },
18624 "onetime": {
18625 "version": "5.1.0",
18626 "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
18627 "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
18628 "requires": {
18629 "mimic-fn": "^2.1.0"
18630 }
18631 },
18632 "p-finally": {
18633 "version": "2.0.1",
18634 "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz",
18635 "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw=="
18636 },
18637 "path-key": {
18638 "version": "3.1.1",
18639 "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
18640 "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
18641 },
18642 "shebang-command": {
18643 "version": "2.0.0",
18644 "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
18645 "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
18646 "requires": {
18647 "shebang-regex": "^3.0.0"
18648 }
18649 },
18650 "shebang-regex": {
18651 "version": "3.0.0",
18652 "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
18653 "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
18654 },
18655 "which": {
18656 "version": "2.0.2",
18657 "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
18658 "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
18659 "requires": {
18660 "isexe": "^2.0.0"
18661 }
18662 }
18663 }
18664 },
18544 "macos-notification-state": { 18665 "macos-notification-state": {
18545 "version": "1.3.1", 18666 "version": "1.3.1",
18546 "resolved": "https://registry.npmjs.org/macos-notification-state/-/macos-notification-state-1.3.1.tgz", 18667 "resolved": "https://registry.npmjs.org/macos-notification-state/-/macos-notification-state-1.3.1.tgz",
@@ -18556,6 +18677,14 @@
18556 "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==", 18677 "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==",
18557 "dev": true 18678 "dev": true
18558 }, 18679 },
18680 "macos-version": {
18681 "version": "5.2.0",
18682 "resolved": "https://registry.npmjs.org/macos-version/-/macos-version-5.2.0.tgz",
18683 "integrity": "sha512-egt1bqVE1evUjCup2QN2F0g42AuVcumdM31xNbABz+uXquYPzWP4OrqDm+HpCfM+6t4JzWrzABQW+MZM+FW+Jg==",
18684 "requires": {
18685 "semver": "^5.6.0"
18686 }
18687 },
18559 "macroable": { 18688 "macroable": {
18560 "version": "1.0.0", 18689 "version": "1.0.0",
18561 "resolved": "https://registry.npmjs.org/macroable/-/macroable-1.0.0.tgz", 18690 "resolved": "https://registry.npmjs.org/macroable/-/macroable-1.0.0.tgz",
@@ -18685,9 +18814,9 @@
18685 } 18814 }
18686 }, 18815 },
18687 "marked": { 18816 "marked": {
18688 "version": "0.7.0", 18817 "version": "0.6.1",
18689 "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", 18818 "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.1.tgz",
18690 "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==" 18819 "integrity": "sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA=="
18691 }, 18820 },
18692 "matchdep": { 18821 "matchdep": {
18693 "version": "2.0.0", 18822 "version": "2.0.0",
@@ -18990,8 +19119,7 @@
18990 "mimic-fn": { 19119 "mimic-fn": {
18991 "version": "2.1.0", 19120 "version": "2.1.0",
18992 "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 19121 "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
18993 "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 19122 "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
18994 "dev": true
18995 }, 19123 },
18996 "mimic-response": { 19124 "mimic-response": {
18997 "version": "1.0.1", 19125 "version": "1.0.1",
@@ -19480,6 +19608,11 @@
19480 "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", 19608 "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
19481 "dev": true 19609 "dev": true
19482 }, 19610 },
19611 "new-github-issue-url": {
19612 "version": "0.2.1",
19613 "resolved": "https://registry.npmjs.org/new-github-issue-url/-/new-github-issue-url-0.2.1.tgz",
19614 "integrity": "sha512-md4cGoxuT4T4d/HDOXbrUHkTKrp/vp+m3aOA7XXVYwNsUNMK49g3SQicTSeV5GIz/5QVGAeYRAOlyp9OvlgsYA=="
19615 },
19483 "next-tick": { 19616 "next-tick": {
19484 "version": "1.0.0", 19617 "version": "1.0.0",
19485 "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", 19618 "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
@@ -24962,6 +25095,11 @@
24962 "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", 25095 "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
24963 "dev": true 25096 "dev": true
24964 }, 25097 },
25098 "strip-final-newline": {
25099 "version": "2.0.0",
25100 "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
25101 "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
25102 },
24965 "strip-indent": { 25103 "strip-indent": {
24966 "version": "2.0.0", 25104 "version": "2.0.0",
24967 "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", 25105 "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
diff --git a/package.json b/package.json
index c267f6ae2..5c9be8488 100644
--- a/package.json
+++ b/package.json
@@ -77,6 +77,7 @@
77 "electron-react-titlebar": "0.8.2", 77 "electron-react-titlebar": "0.8.2",
78 "electron-spellchecker": "2.2.1", 78 "electron-spellchecker": "2.2.1",
79 "electron-updater": "4.2.5", 79 "electron-updater": "4.2.5",
80 "electron-util": "0.14.0",
80 "electron-window-state": "5.0.3", 81 "electron-window-state": "5.0.3",
81 "fs-extra": "7.0.1", 82 "fs-extra": "7.0.1",
82 "gulp-csso": "3.0.1", 83 "gulp-csso": "3.0.1",
@@ -85,8 +86,10 @@
85 "gulp-uglify": "3.0.2", 86 "gulp-uglify": "3.0.2",
86 "hex-to-rgba": "1.0.2", 87 "hex-to-rgba": "1.0.2",
87 "jsonwebtoken": "8.5.1", 88 "jsonwebtoken": "8.5.1",
88 "lodash": "4.17.15", 89 "lodash": "^4.17.4",
89 "marked": "0.7.0", 90 "mac-screen-capture-permissions": "1.1.0",
91 "macos-version": "5.2.0",
92 "marked": "0.6.1",
90 "mdi": "^1.9.33", 93 "mdi": "^1.9.33",
91 "mime-types": "2.1.25", 94 "mime-types": "2.1.25",
92 "mobx": "5.15.0", 95 "mobx": "5.15.0",
@@ -138,7 +141,7 @@
138 "@babel/preset-react": "7.7.4", 141 "@babel/preset-react": "7.7.4",
139 "@babel/register": "7.7.4", 142 "@babel/register": "7.7.4",
140 "@types/classnames": "^2.2.6", 143 "@types/classnames": "^2.2.6",
141 "@types/color": "^3.0.0", 144 "@types/color": "3.0.0",
142 "@types/color-convert": "^1.9.0", 145 "@types/color-convert": "^1.9.0",
143 "@types/jss": "^9.5.7", 146 "@types/jss": "^9.5.7",
144 "@types/lodash": "4.14.149", 147 "@types/lodash": "4.14.149",
diff --git a/packages/theme/package-lock.json b/packages/theme/package-lock.json
index f74af2f24..85a87cf04 100644
--- a/packages/theme/package-lock.json
+++ b/packages/theme/package-lock.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "@meetfranz/theme", 2 "name": "@meetfranz/theme",
3 "version": "1.0.4", 3 "version": "1.0.14",
4 "lockfileVersion": 1, 4 "lockfileVersion": 1,
5 "requires": true, 5 "requires": true,
6 "dependencies": { 6 "dependencies": {
diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts
index 30cc19d99..b436d92f9 100644
--- a/packages/theme/src/themes/dark/index.ts
+++ b/packages/theme/src/themes/dark/index.ts
@@ -65,7 +65,7 @@ export const selectOptionItemHoverColor = selectColor;
65export const selectSearchColor = inputBackground; 65export const selectSearchColor = inputBackground;
66 66
67// Modal 67// Modal
68export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string(); 68export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.9).rgb().string();
69export const colorModalBackground = legacyStyles.darkThemeGrayDark; 69export const colorModalBackground = legacyStyles.darkThemeGrayDark;
70 70
71// Services 71// Services
diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts
index edf56f21e..f8dc36574 100644
--- a/packages/theme/src/themes/default/index.ts
+++ b/packages/theme/src/themes/default/index.ts
@@ -1,7 +1,6 @@
1import color from 'color'; 1import color from 'color';
2import { cloneDeep } from 'lodash'; 2import { cloneDeep } from 'lodash';
3 3
4import { darkgreen } from 'color-name';
5import * as legacyStyles from '../legacy'; 4import * as legacyStyles from '../legacy';
6 5
7export interface IStyleTypes { 6export interface IStyleTypes {
@@ -145,7 +144,7 @@ export const badgeFontSize = uiFontSize - 2;
145export const badgeBorderRadius = 50; 144export const badgeBorderRadius = 50;
146 145
147// Modal 146// Modal
148export const colorModalOverlayBackground = color('#000').alpha(0.5).rgb().string(); 147export const colorModalOverlayBackground = color('#000').alpha(0.8).rgb().string();
149export const colorModalBackground = colorContentBackground; 148export const colorModalBackground = colorContentBackground;
150 149
151// Services 150// Services
diff --git a/src/components/services/content/ConnectionLostBanner.js b/src/components/services/content/ConnectionLostBanner.js
new file mode 100644
index 000000000..9609a65b1
--- /dev/null
+++ b/src/components/services/content/ConnectionLostBanner.js
@@ -0,0 +1,119 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { Icon } from '@meetfranz/ui';
6import { intlShape, defineMessages } from 'react-intl';
7
8import {
9 mdiAlert,
10} from '@mdi/js';
11import { LIVE_API_WEBSITE } from '../../../config';
12// import { Button } from '@meetfranz/forms';
13
14const messages = defineMessages({
15 text: {
16 id: 'connectionLostBanner.message',
17 defaultMessage: '!!!Oh no! Franz lost the connection to {name}.',
18 },
19 moreInformation: {
20 id: 'connectionLostBanner.informationLink',
21 defaultMessage: '!!!What happened?',
22 },
23 cta: {
24 id: 'connectionLostBanner.cta',
25 defaultMessage: '!!!Reload Service',
26 },
27});
28
29const styles = theme => ({
30 root: {
31 background: theme.colorBackground,
32 borderRadius: theme.borderRadius,
33 position: 'absolute',
34 zIndex: 300,
35 height: 50,
36 display: 'flex',
37 flexDirection: 'row',
38 alignItems: 'center',
39 bottom: 10,
40 right: 10,
41 justifyContent: 'center',
42 padding: 10,
43 fontSize: 12,
44 },
45 link: {
46 display: 'inline-flex',
47 opacity: 0.7,
48 },
49 button: {
50 transition: 'opacity 0.25s',
51 color: theme.colorText,
52 border: [1, 'solid', theme.colorText],
53 borderRadius: theme.borderRadiusSmall,
54 padding: 4,
55 fontSize: 12,
56 marginLeft: 15,
57
58 '&:hover': {
59 opacity: 0.8,
60 },
61 },
62 icon: {
63 marginRight: 10,
64 fill: theme.styleTypes.danger.accent,
65 },
66});
67
68@injectSheet(styles) @observer
69class ConnectionLostBanner extends Component {
70 static propTypes = {
71 classes: PropTypes.object.isRequired,
72 name: PropTypes.string.isRequired,
73 reload: PropTypes.func.isRequired,
74 }
75
76 static contextTypes = {
77 intl: intlShape,
78 };
79
80 inputRef = React.createRef();
81
82 render() {
83 const {
84 classes,
85 name,
86 reload,
87 } = this.props;
88
89 const { intl } = this.context;
90
91 return (
92 <div className={classes.root}>
93 <Icon
94 icon={mdiAlert}
95 className={classes.icon}
96 />
97 <p>
98 {intl.formatMessage(messages.text, { name })}
99 <br />
100 <a
101 href={`${LIVE_API_WEBSITE}/support#what-does-franz-lost-the-connection-to-service-mean`}
102 className={classes.link}
103 >
104 {intl.formatMessage(messages.moreInformation)}
105 </a>
106 </p>
107 <button
108 type="button"
109 className={classes.button}
110 onClick={reload}
111 >
112 {intl.formatMessage(messages.cta)}
113 </button>
114 </div>
115 );
116 }
117}
118
119export default ConnectionLostBanner;
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index f6832038a..d91016c71 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -193,7 +193,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
193 </Fragment> 193 </Fragment>
194 ) : ( 194 ) : (
195 <> 195 <>
196 {!service.isHibernating ? ( 196 {(!service.isHibernating || service.disableHibernation) ? (
197 <> 197 <>
198 {showNavBar && ( 198 {showNavBar && (
199 <WebControlsScreen service={service} /> 199 <WebControlsScreen service={service} />
@@ -203,6 +203,12 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
203 setWebviewReference={setWebviewReference} 203 setWebviewReference={setWebviewReference}
204 detachService={detachService} 204 detachService={detachService}
205 /> 205 />
206 {/* {service.lostRecipeConnection && (
207 <ConnectionLostBanner
208 name={service.name}
209 reload={reload}
210 />
211 )} */}
206 </> 212 </>
207 ) : ( 213 ) : (
208 <div> 214 <div>
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
index 36338a910..ea7a66a62 100644
--- a/src/components/services/tabs/TabItem.js
+++ b/src/components/services/tabs/TabItem.js
@@ -145,7 +145,7 @@ class TabItem extends Component {
145 145
146 </span> 146 </span>
147 )} 147 )}
148 {service.isHibernating && ( 148 {service.isHibernating && !service.disableHibernation && (
149 <span className="tab-item__message-count hibernating"> 149 <span className="tab-item__message-count hibernating">
150 150
151 </span> 151 </span>
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index bb4f4a76f..4fd1f99ef 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -94,6 +94,10 @@ const messages = defineMessages({
94 id: 'settings.service.form.isMutedInfo', 94 id: 'settings.service.form.isMutedInfo',
95 defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', 95 defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted',
96 }, 96 },
97 disableHibernationInfo: {
98 id: 'settings.service.form.disableHibernationInfo',
99 defaultMessage: '!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.',
100 },
97 headlineNotifications: { 101 headlineNotifications: {
98 id: 'settings.service.form.headlineNotifications', 102 id: 'settings.service.form.headlineNotifications',
99 defaultMessage: '!!!Notifications', 103 defaultMessage: '!!!Notifications',
@@ -154,6 +158,7 @@ export default @observer class EditServiceForm extends Component {
154 isProxyFeatureEnabled: PropTypes.bool.isRequired, 158 isProxyFeatureEnabled: PropTypes.bool.isRequired,
155 isServiceProxyIncludedInCurrentPlan: PropTypes.bool.isRequired, 159 isServiceProxyIncludedInCurrentPlan: PropTypes.bool.isRequired,
156 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired, 160 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
161 isHibernationFeatureActive: PropTypes.bool.isRequired,
157 }; 162 };
158 163
159 static defaultProps = { 164 static defaultProps = {
@@ -219,6 +224,7 @@ export default @observer class EditServiceForm extends Component {
219 isProxyFeatureEnabled, 224 isProxyFeatureEnabled,
220 isServiceProxyIncludedInCurrentPlan, 225 isServiceProxyIncludedInCurrentPlan,
221 isSpellcheckerIncludedInCurrentPlan, 226 isSpellcheckerIncludedInCurrentPlan,
227 isHibernationFeatureActive,
222 } = this.props; 228 } = this.props;
223 const { intl } = this.context; 229 const { intl } = this.context;
224 230
@@ -365,6 +371,14 @@ export default @observer class EditServiceForm extends Component {
365 <div className="settings__settings-group"> 371 <div className="settings__settings-group">
366 <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> 372 <h3>{intl.formatMessage(messages.headlineGeneral)}</h3>
367 <Toggle field={form.$('isEnabled')} /> 373 <Toggle field={form.$('isEnabled')} />
374 {isHibernationFeatureActive && (
375 <>
376 <Toggle field={form.$('disableHibernation')} />
377 <p className="settings__help">
378 {intl.formatMessage(messages.disableHibernationInfo)}
379 </p>
380 </>
381 )}
368 <Toggle field={form.$('isDarkModeEnabled')} /> 382 <Toggle field={form.$('isDarkModeEnabled')} />
369 {form.$('isDarkModeEnabled').value 383 {form.$('isDarkModeEnabled').value
370 && ( 384 && (
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js
index f1039709c..dbc2a9078 100644
--- a/src/components/ui/FeatureList.js
+++ b/src/components/ui/FeatureList.js
@@ -66,6 +66,10 @@ const messages = defineMessages({
66 id: 'pricing.features.adFree', 66 id: 'pricing.features.adFree',
67 defaultMessage: '!!!Forever ad-free', 67 defaultMessage: '!!!Forever ad-free',
68 }, 68 },
69 appDelayEnabled: {
70 id: 'pricing.features.appDelaysEnabled',
71 defaultMessage: '!!!Occasional Waiting Screens',
72 },
69}); 73});
70 74
71export class FeatureList extends Component { 75export class FeatureList extends Component {
@@ -96,6 +100,7 @@ export class FeatureList extends Component {
96 const features = []; 100 const features = [];
97 if (plan === PLANS.FREE) { 101 if (plan === PLANS.FREE) {
98 features.push( 102 features.push(
103 messages.appDelayEnabled,
99 messages.upToThreeServices, 104 messages.upToThreeServices,
100 messages.availableRecipes, 105 messages.availableRecipes,
101 messages.accountSync, 106 messages.accountSync,
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
index 560068efc..14c1ef41e 100644
--- a/src/containers/settings/EditServiceScreen.js
+++ b/src/containers/settings/EditServiceScreen.js
@@ -33,6 +33,10 @@ const messages = defineMessages({
33 id: 'settings.service.form.enableService', 33 id: 'settings.service.form.enableService',
34 defaultMessage: '!!!Enable service', 34 defaultMessage: '!!!Enable service',
35 }, 35 },
36 disableHibernation: {
37 id: 'settings.service.form.disableHibernation',
38 defaultMessage: '!!!Disable hibernation',
39 },
36 enableNotification: { 40 enableNotification: {
37 id: 'settings.service.form.enableNotification', 41 id: 'settings.service.form.enableNotification',
38 defaultMessage: '!!!Enable Notifications', 42 defaultMessage: '!!!Enable Notifications',
@@ -134,8 +138,11 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
134 138
135 const { 139 const {
136 stores, 140 stores,
141 router,
137 } = this.props; 142 } = this.props;
138 143
144 const { action } = router.params;
145
139 let defaultSpellcheckerLanguage = SPELLCHECKER_LOCALES[stores.settings.app.spellcheckerLanguage]; 146 let defaultSpellcheckerLanguage = SPELLCHECKER_LOCALES[stores.settings.app.spellcheckerLanguage];
140 147
141 if (stores.settings.app.spellcheckerLanguage === 'automatic') { 148 if (stores.settings.app.spellcheckerLanguage === 'automatic') {
@@ -160,6 +167,11 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
160 value: service.isEnabled, 167 value: service.isEnabled,
161 default: true, 168 default: true,
162 }, 169 },
170 disableHibernation: {
171 label: intl.formatMessage(messages.disableHibernation),
172 value: action !== 'edit' ? false : service.disableHibernation,
173 default: false,
174 },
163 isNotificationEnabled: { 175 isNotificationEnabled: {
164 label: intl.formatMessage(messages.enableNotification), 176 label: intl.formatMessage(messages.enableNotification),
165 value: service.isNotificationEnabled, 177 value: service.isNotificationEnabled,
@@ -327,7 +339,9 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
327 } 339 }
328 340
329 render() { 341 render() {
330 const { recipes, services, user } = this.props.stores; 342 const {
343 recipes, services, user, settings,
344 } = this.props.stores;
331 const { action } = this.props.router.params; 345 const { action } = this.props.router.params;
332 346
333 let recipe; 347 let recipe;
@@ -381,6 +395,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
381 isProxyFeatureEnabled={proxyFeature.isEnabled} 395 isProxyFeatureEnabled={proxyFeature.isEnabled}
382 isServiceProxyIncludedInCurrentPlan={proxyFeature.isIncludedInCurrentPlan} 396 isServiceProxyIncludedInCurrentPlan={proxyFeature.isIncludedInCurrentPlan}
383 isSpellcheckerIncludedInCurrentPlan={spellcheckerFeature.isIncludedInCurrentPlan} 397 isSpellcheckerIncludedInCurrentPlan={spellcheckerFeature.isIncludedInCurrentPlan}
398 isHibernationFeatureActive={settings.app.hibernate}
384 /> 399 />
385 </ErrorBoundary> 400 </ErrorBoundary>
386 ); 401 );
diff --git a/src/electron/macOSPermissions.js b/src/electron/macOSPermissions.js
new file mode 100644
index 000000000..4ba6a7619
--- /dev/null
+++ b/src/electron/macOSPermissions.js
@@ -0,0 +1,14 @@
1import { systemPreferences } from 'electron';
2import {
3 hasScreenCapturePermission,
4 hasPromptedForPermission,
5} from 'mac-screen-capture-permissions';
6
7export default function () {
8 systemPreferences.askForMediaAccess('camera');
9 systemPreferences.askForMediaAccess('microphone');
10
11 if (!hasPromptedForPermission()) {
12 hasScreenCapturePermission();
13 }
14}
diff --git a/src/features/announcements/components/AnnouncementScreen.js b/src/features/announcements/components/AnnouncementScreen.js
index 38de2dbc8..2f25e7139 100644
--- a/src/features/announcements/components/AnnouncementScreen.js
+++ b/src/features/announcements/components/AnnouncementScreen.js
@@ -192,6 +192,11 @@ class AnnouncementScreen extends Component {
192 stores: PropTypes.shape({ 192 stores: PropTypes.shape({
193 ui: PropTypes.instanceOf(UIStore).isRequired, 193 ui: PropTypes.instanceOf(UIStore).isRequired,
194 }).isRequired, 194 }).isRequired,
195 actions: PropTypes.shape({
196 app: PropTypes.shape({
197 openExternalUrl: PropTypes.func.isRequired,
198 }).isRequired,
199 }).isRequired,
195 }; 200 };
196 201
197 static contextTypes = { 202 static contextTypes = {
@@ -199,7 +204,7 @@ class AnnouncementScreen extends Component {
199 }; 204 };
200 205
201 render() { 206 render() {
202 const { classes, stores } = this.props; 207 const { classes, stores, actions } = this.props;
203 const { intl } = this.context; 208 const { intl } = this.context;
204 const { changelog, announcement } = announcementsStore; 209 const { changelog, announcement } = announcementsStore;
205 const themeImage = stores.ui.isDarkThemeActive ? 'dark' : 'light'; 210 const themeImage = stores.ui.isDarkThemeActive ? 'dark' : 'light';
@@ -223,14 +228,23 @@ class AnnouncementScreen extends Component {
223 __html: marked(announcement.main.text, markedOptions), 228 __html: marked(announcement.main.text, markedOptions),
224 }} 229 }}
225 /> 230 />
226 <div className={classes.mainCtaButton}> 231 {(announcement.main.cta.label || announcement.main.cta.href) && (
227 <Button 232 <div className={classes.mainCtaButton}>
228 label={announcement.main.cta.label} 233 <Button
229 onClick={() => { 234 label={announcement.main.cta.label}
230 window.location.href = `#${announcement.main.cta.href}`; 235 onClick={() => {
231 }} 236 const {
232 /> 237 href,
233 </div> 238 } = announcement.main.cta;
239 if (announcement.main.cta.href.startsWith('http')) {
240 actions.app.openExternalUrl({ url: href });
241 } else {
242 window.location.href = `#${href}`;
243 }
244 }}
245 />
246 </div>
247 )}
234 </div> 248 </div>
235 </div> 249 </div>
236 </div> 250 </div>
@@ -250,7 +264,14 @@ class AnnouncementScreen extends Component {
250 <Button 264 <Button
251 label={announcement.spotlight.cta.label} 265 label={announcement.spotlight.cta.label}
252 onClick={() => { 266 onClick={() => {
253 window.location.href = `#${announcement.spotlight.cta.href}`; 267 const {
268 href,
269 } = announcement.spotlight.cta;
270 if (announcement.spotlight.cta.href.startsWith('http')) {
271 actions.app.openExternalUrl({ url: href });
272 } else {
273 window.location.href = `#${href}`;
274 }
254 }} 275 }}
255 /> 276 />
256 </div> 277 </div>
diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js
index ec061377b..3855fedf1 100644
--- a/src/features/planSelection/components/PlanItem.js
+++ b/src/features/planSelection/components/PlanItem.js
@@ -49,6 +49,7 @@ const styles = theme => ({
49 priceWrapper: { 49 priceWrapper: {
50 height: 50, 50 height: 50,
51 marginBottom: 0, 51 marginBottom: 0,
52 marginTop: ({ text }) => (!text ? 15 : 0),
52 }, 53 },
53 price: { 54 price: {
54 fontSize: 50, 55 fontSize: 50,
@@ -64,7 +65,7 @@ const styles = theme => ({
64 cta: { 65 cta: {
65 background: theme.styleTypes.primary.accent, 66 background: theme.styleTypes.primary.accent,
66 color: theme.styleTypes.primary.contrast, 67 color: theme.styleTypes.primary.contrast,
67 margin: [40, 'auto', 0, 'auto'], 68 margin: [30, 'auto', 0, 'auto'],
68 }, 69 },
69 divider: { 70 divider: {
70 width: 40, 71 width: 40,
@@ -77,10 +78,14 @@ const styles = theme => ({
77 background: color(theme.styleTypes.primary.accent).darken(0.25).hex(), 78 background: color(theme.styleTypes.primary.accent).darken(0.25).hex(),
78 color: theme.styleTypes.primary.contrast, 79 color: theme.styleTypes.primary.contrast,
79 position: 'relative', 80 position: 'relative',
81 height: 'auto',
80 }, 82 },
81 content: { 83 content: {
82 padding: [10, 20, 20], 84 padding: [10, 20, 20],
83 background: '#EFEFEF', 85 background: '#EFEFEF',
86 display: 'flex',
87 flexDirection: 'column',
88 justifyContent: 'space-between',
84 }, 89 },
85 simpleCTA: { 90 simpleCTA: {
86 background: 'none', 91 background: 'none',
@@ -167,10 +172,14 @@ export default @observer @injectSheet(styles) class PlanItem extends Component {
167 </div> 172 </div>
168 )} 173 )}
169 <H2 className={classes.planName}>{name}</H2> 174 <H2 className={classes.planName}>{name}</H2>
170 <p className={classes.text}> 175 {text && (
171 {text} 176 <>
172 </p> 177 <p className={classes.text}>
173 <hr className={classes.divider} /> 178 {text}
179 </p>
180 <hr className={classes.divider} />
181 </>
182 )}
174 <p className={classes.priceWrapper}> 183 <p className={classes.priceWrapper}>
175 <span className={classes.currency}>{currency}</span> 184 <span className={classes.currency}>{currency}</span>
176 <span className={classes.price}> 185 <span className={classes.price}>
diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js
index 4bf5238dd..6f0dd30ad 100644
--- a/src/features/planSelection/components/PlanSelection.js
+++ b/src/features/planSelection/components/PlanSelection.js
@@ -6,7 +6,7 @@ import { defineMessages, intlShape } from 'react-intl';
6import { H1, H2, Icon } from '@meetfranz/ui'; 6import { H1, H2, Icon } from '@meetfranz/ui';
7import color from 'color'; 7import color from 'color';
8 8
9import { mdiRocket, mdiArrowRight } from '@mdi/js'; 9import { mdiArrowRight } from '@mdi/js';
10import PlanItem from './PlanItem'; 10import PlanItem from './PlanItem';
11import { i18nPlanName } from '../../../helpers/plan-helpers'; 11import { i18nPlanName } from '../../../helpers/plan-helpers';
12import { PLANS } from '../../../config'; 12import { PLANS } from '../../../config';
@@ -79,10 +79,10 @@ const styles = theme => ({
79 overflowY: 'scroll', 79 overflowY: 'scroll',
80 }, 80 },
81 container: { 81 container: {
82 width: '80%', 82 // width: '80%',
83 height: 'auto', 83 height: 'auto',
84 background: theme.styleTypes.primary.accent, 84 // background: theme.styleTypes.primary.accent,
85 padding: 40, 85 // padding: 40,
86 borderRadius: theme.borderRadius, 86 borderRadius: theme.borderRadius,
87 maxWidth: 1000, 87 maxWidth: 1000,
88 88
@@ -104,23 +104,6 @@ const styles = theme => ({
104 boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()], 104 boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()],
105 }, 105 },
106 }, 106 },
107 bigIcon: {
108 background: theme.styleTypes.danger.accent,
109 width: 120,
110 height: 120,
111 display: 'flex',
112 alignItems: 'center',
113 borderRadius: '100%',
114 justifyContent: 'center',
115 margin: [-100, 'auto', 20],
116
117 '& svg': {
118 width: '80px !important',
119 height: '80px !important',
120 filter: 'drop-shadow( 0px 2px 3px rgba(0, 0, 0, 0.3))',
121 fill: theme.styleTypes.danger.contrast,
122 },
123 },
124 headline: { 107 headline: {
125 fontSize: 40, 108 fontSize: 40,
126 }, 109 },
@@ -158,7 +141,7 @@ const styles = theme => ({
158 overflow: 'scroll-x', 141 overflow: 'scroll-x',
159 }, 142 },
160 featuredPlan: { 143 featuredPlan: {
161 transform: 'scale(1.05)', 144 transform: ({ isPersonalPlanAvailable }) => (isPersonalPlanAvailable ? 'scale(1.05)' : null),
162 }, 145 },
163 disclaimer: { 146 disclaimer: {
164 textAlign: 'right', 147 textAlign: 'right',
@@ -177,8 +160,13 @@ class PlanSelection extends Component {
177 upgradeAccount: PropTypes.func.isRequired, 160 upgradeAccount: PropTypes.func.isRequired,
178 stayOnFree: PropTypes.func.isRequired, 161 stayOnFree: PropTypes.func.isRequired,
179 hadSubscription: PropTypes.bool.isRequired, 162 hadSubscription: PropTypes.bool.isRequired,
163 isPersonalPlanAvailable: PropTypes.bool,
180 }; 164 };
181 165
166 static defaultProps = {
167 isPersonalPlanAvailable: true,
168 }
169
182 static contextTypes = { 170 static contextTypes = {
183 intl: intlShape, 171 intl: intlShape,
184 }; 172 };
@@ -196,6 +184,7 @@ class PlanSelection extends Component {
196 upgradeAccount, 184 upgradeAccount,
197 stayOnFree, 185 stayOnFree,
198 hadSubscription, 186 hadSubscription,
187 isPersonalPlanAvailable,
199 } = this.props; 188 } = this.props;
200 189
201 const { intl } = this.context; 190 const { intl } = this.context;
@@ -206,15 +195,14 @@ class PlanSelection extends Component {
206 className={classes.root} 195 className={classes.root}
207 > 196 >
208 <div className={classes.container}> 197 <div className={classes.container}>
209 <div className={classes.bigIcon}>
210 <Icon icon={mdiRocket} />
211 </div>
212 <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1> 198 <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1>
213 <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2> 199 {isPersonalPlanAvailable && (
200 <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2>
201 )}
214 <div className={classes.plans}> 202 <div className={classes.plans}>
215 <PlanItem 203 <PlanItem
216 name={i18nPlanName(PLANS.FREE, intl)} 204 name={i18nPlanName(PLANS.FREE, intl)}
217 text={intl.formatMessage(messages.textFree)} 205 text={isPersonalPlanAvailable ? intl.formatMessage(messages.textFree) : null}
218 price={0} 206 price={0}
219 currency={currency} 207 currency={currency}
220 ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)} 208 ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)}
@@ -228,33 +216,35 @@ class PlanSelection extends Component {
228 </PlanItem> 216 </PlanItem>
229 <PlanItem 217 <PlanItem
230 name={i18nPlanName(plans.pro.yearly.id, intl)} 218 name={i18nPlanName(plans.pro.yearly.id, intl)}
231 text={intl.formatMessage(messages.textProfessional)} 219 text={isPersonalPlanAvailable ? intl.formatMessage(messages.textProfessional) : null}
232 price={plans.pro.yearly.price} 220 price={plans.pro.yearly.price}
233 currency={currency} 221 currency={currency}
234 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)} 222 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)}
235 upgrade={() => upgradeAccount(plans.pro.yearly.id)} 223 upgrade={() => upgradeAccount(plans.pro.yearly.id)}
236 className={classes.featuredPlan} 224 className={classes.featuredPlan}
237 perUser 225 perUser
238 bestValue 226 bestValue={isPersonalPlanAvailable}
239 >
240 <FeatureList
241 plan={PLANS.PRO}
242 className={classes.featureList}
243 />
244 </PlanItem>
245 <PlanItem
246 name={i18nPlanName(plans.personal.yearly.id, intl)}
247 text={intl.formatMessage(messages.textPersonal)}
248 price={plans.personal.yearly.price}
249 currency={currency}
250 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)}
251 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
252 > 227 >
253 <FeatureList 228 <FeatureList
254 plan={PLANS.PERSONAL} 229 plan={isPersonalPlanAvailable ? PLANS.PRO : null}
255 className={classes.featureList} 230 className={classes.featureList}
256 /> 231 />
257 </PlanItem> 232 </PlanItem>
233 {isPersonalPlanAvailable && (
234 <PlanItem
235 name={i18nPlanName(plans.personal.yearly.id, intl)}
236 text={intl.formatMessage(messages.textPersonal)}
237 price={plans.personal.yearly.price}
238 currency={currency}
239 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)}
240 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
241 >
242 <FeatureList
243 plan={PLANS.PERSONAL}
244 className={classes.featureList}
245 />
246 </PlanItem>
247 )}
258 </div> 248 </div>
259 <div className={classes.footer}> 249 <div className={classes.footer}>
260 <a 250 <a
diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js
index d202c924e..e4d85cda5 100644
--- a/src/features/planSelection/containers/PlanSelectionScreen.js
+++ b/src/features/planSelection/containers/PlanSelectionScreen.js
@@ -53,7 +53,8 @@ class PlanSelectionScreen extends Component {
53 const { intl } = this.context; 53 const { intl } = this.context;
54 54
55 const { user, features } = this.props.stores; 55 const { user, features } = this.props.stores;
56 const { plans, currency } = features.features.pricingConfig; 56 const { isPersonalPlanAvailable, pricingConfig } = features.features;
57 const { plans, currency } = pricingConfig;
57 const { activateTrial } = this.props.actions.user; 58 const { activateTrial } = this.props.actions.user;
58 const { downgradeAccount, hideOverlay } = this.props.actions.planSelection; 59 const { downgradeAccount, hideOverlay } = this.props.actions.planSelection;
59 60
@@ -95,6 +96,7 @@ class PlanSelectionScreen extends Component {
95 }} 96 }}
96 subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded} 97 subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded}
97 hadSubscription={user.data.hadSubscription} 98 hadSubscription={user.data.hadSubscription}
99 isPersonalPlanAvailable={isPersonalPlanAvailable}
98 /> 100 />
99 </ErrorBoundary> 101 </ErrorBoundary>
100 ); 102 );
diff --git a/src/helpers/plan-helpers.js b/src/helpers/plan-helpers.js
index ee22e4471..b474f8bbd 100644
--- a/src/helpers/plan-helpers.js
+++ b/src/helpers/plan-helpers.js
@@ -20,6 +20,10 @@ const messages = defineMessages({
20 }, 20 },
21}); 21});
22 22
23export function cleanupPlanId(id) {
24 return id.replace(/(.*)-x[0-9]/, '$1');
25}
26
23export function i18nPlanName(planId, intl) { 27export function i18nPlanName(planId, intl) {
24 if (!planId) { 28 if (!planId) {
25 throw new Error('planId is required'); 29 throw new Error('planId is required');
@@ -29,7 +33,9 @@ export function i18nPlanName(planId, intl) {
29 throw new Error('intl context is required'); 33 throw new Error('intl context is required');
30 } 34 }
31 35
32 const plan = PLANS_MAPPING[planId]; 36 const id = cleanupPlanId(planId);
37
38 const plan = PLANS_MAPPING[id];
33 39
34 return intl.formatMessage(messages[plan]); 40 return intl.formatMessage(messages[plan]);
35} 41}
@@ -39,7 +45,9 @@ export function getPlan(planId) {
39 throw new Error('planId is required'); 45 throw new Error('planId is required');
40 } 46 }
41 47
42 const plan = PLANS_MAPPING[planId]; 48 const id = cleanupPlanId(planId);
49
50 const plan = PLANS_MAPPING[id];
43 51
44 return plan; 52 return plan;
45} 53}
diff --git a/src/helpers/userAgent-helpers.js b/src/helpers/userAgent-helpers.js
new file mode 100644
index 000000000..15edc1054
--- /dev/null
+++ b/src/helpers/userAgent-helpers.js
@@ -0,0 +1,45 @@
1import { remote, app } from 'electron';
2import os from 'os';
3import macosVersion from 'macos-version';
4import { isMac, isWindows } from '../environment';
5
6// This helper gets included from the backend and frontend but we only need to use "remote"
7// if we are in the frontend
8const ferdiVersion = remote && remote.app ? remote.app.getVersion() : app.getVersion();
9
10function macOS() {
11 const version = macosVersion();
12
13 return `Macintosh; Intel Mac OS X ${version.replace(/\./g, '_')}`;
14}
15
16function windows() {
17 const version = os.release();
18 const [majorVersion, minorVersion] = version.split('.');
19 return `Windows NT ${majorVersion}.${minorVersion}; Win64; x64`;
20}
21
22function linux() {
23 return 'X11; Ubuntu; Linux x86_64';
24}
25
26export default function userAgent(removeChromeVersion = false) {
27 let platformString = '';
28
29 if (isMac) {
30 platformString = macOS();
31 } else if (isWindows) {
32 platformString = windows();
33 } else {
34 platformString = linux();
35 }
36
37 let applicationString = '';
38 if (!removeChromeVersion) {
39 applicationString = ` Ferdi/${ferdiVersion} (Electron ${process.versions.electron})`;
40 }
41
42 // TODO: Update AppleWebKit and Safari version after electron update
43 return `Mozilla/5.0 (${platformString}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome${!removeChromeVersion ? `/${process.versions.chrome}` : ''} Safari/537.36${applicationString}`;
44 // Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) old-airport-include/1.0.0 Chrome Electron/7.1.7 Safari/537.36
45}
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index 56d0b1971..f3ff7caf3 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -1193,6 +1193,190 @@
1193 { 1193 {
1194 "descriptors": [ 1194 "descriptors": [
1195 { 1195 {
1196 "defaultMessage": "!!!Home",
1197 "end": {
1198 "column": 3,
1199 "line": 16
1200 },
1201 "file": "src/components/services/content/ConnectionBanner.js",
1202 "id": "webControls.goHome",
1203 "start": {
1204 "column": 10,
1205 "line": 13
1206 }
1207 },
1208 {
1209 "defaultMessage": "!!!Open in Browser",
1210 "end": {
1211 "column": 3,
1212 "line": 20
1213 },
1214 "file": "src/components/services/content/ConnectionBanner.js",
1215 "id": "webControls.openInBrowser",
1216 "start": {
1217 "column": 17,
1218 "line": 17
1219 }
1220 },
1221 {
1222 "defaultMessage": "!!!Back",
1223 "end": {
1224 "column": 3,
1225 "line": 24
1226 },
1227 "file": "src/components/services/content/ConnectionBanner.js",
1228 "id": "webControls.back",
1229 "start": {
1230 "column": 8,
1231 "line": 21
1232 }
1233 },
1234 {
1235 "defaultMessage": "!!!Forward",
1236 "end": {
1237 "column": 3,
1238 "line": 28
1239 },
1240 "file": "src/components/services/content/ConnectionBanner.js",
1241 "id": "webControls.forward",
1242 "start": {
1243 "column": 11,
1244 "line": 25
1245 }
1246 },
1247 {
1248 "defaultMessage": "!!!Reload",
1249 "end": {
1250 "column": 3,
1251 "line": 32
1252 },
1253 "file": "src/components/services/content/ConnectionBanner.js",
1254 "id": "webControls.reload",
1255 "start": {
1256 "column": 10,
1257 "line": 29
1258 }
1259 }
1260 ],
1261 "path": "src/components/services/content/ConnectionBanner.json"
1262 },
1263 {
1264 "descriptors": [
1265 {
1266 "defaultMessage": "!!!Home",
1267 "end": {
1268 "column": 3,
1269 "line": 16
1270 },
1271 "file": "src/components/services/content/ConnectionLost.js",
1272 "id": "webControls.goHome",
1273 "start": {
1274 "column": 10,
1275 "line": 13
1276 }
1277 },
1278 {
1279 "defaultMessage": "!!!Open in Browser",
1280 "end": {
1281 "column": 3,
1282 "line": 20
1283 },
1284 "file": "src/components/services/content/ConnectionLost.js",
1285 "id": "webControls.openInBrowser",
1286 "start": {
1287 "column": 17,
1288 "line": 17
1289 }
1290 },
1291 {
1292 "defaultMessage": "!!!Back",
1293 "end": {
1294 "column": 3,
1295 "line": 24
1296 },
1297 "file": "src/components/services/content/ConnectionLost.js",
1298 "id": "webControls.back",
1299 "start": {
1300 "column": 8,
1301 "line": 21
1302 }
1303 },
1304 {
1305 "defaultMessage": "!!!Forward",
1306 "end": {
1307 "column": 3,
1308 "line": 28
1309 },
1310 "file": "src/components/services/content/ConnectionLost.js",
1311 "id": "webControls.forward",
1312 "start": {
1313 "column": 11,
1314 "line": 25
1315 }
1316 },
1317 {
1318 "defaultMessage": "!!!Reload",
1319 "end": {
1320 "column": 3,
1321 "line": 32
1322 },
1323 "file": "src/components/services/content/ConnectionLost.js",
1324 "id": "webControls.reload",
1325 "start": {
1326 "column": 10,
1327 "line": 29
1328 }
1329 }
1330 ],
1331 "path": "src/components/services/content/ConnectionLost.json"
1332 },
1333 {
1334 "descriptors": [
1335 {
1336 "defaultMessage": "!!!Oh no! Franz lost the connection to {name}.",
1337 "end": {
1338 "column": 3,
1339 "line": 18
1340 },
1341 "file": "src/components/services/content/ConnectionLostBanner.js",
1342 "id": "connectionLostBanner.message",
1343 "start": {
1344 "column": 8,
1345 "line": 15
1346 }
1347 },
1348 {
1349 "defaultMessage": "!!!What happened?",
1350 "end": {
1351 "column": 3,
1352 "line": 22
1353 },
1354 "file": "src/components/services/content/ConnectionLostBanner.js",
1355 "id": "connectionLostBanner.informationLink",
1356 "start": {
1357 "column": 19,
1358 "line": 19
1359 }
1360 },
1361 {
1362 "defaultMessage": "!!!Reload Service",
1363 "end": {
1364 "column": 3,
1365 "line": 26
1366 },
1367 "file": "src/components/services/content/ConnectionLostBanner.js",
1368 "id": "connectionLostBanner.cta",
1369 "start": {
1370 "column": 7,
1371 "line": 23
1372 }
1373 }
1374 ],
1375 "path": "src/components/services/content/ConnectionLostBanner.json"
1376 },
1377 {
1378 "descriptors": [
1379 {
1196 "defaultMessage": "!!!Oh no!", 1380 "defaultMessage": "!!!Oh no!",
1197 "end": { 1381 "end": {
1198 "column": 3, 1382 "column": 3,
@@ -1434,6 +1618,76 @@
1434 { 1618 {
1435 "descriptors": [ 1619 "descriptors": [
1436 { 1620 {
1621 "defaultMessage": "!!!Home",
1622 "end": {
1623 "column": 3,
1624 "line": 16
1625 },
1626 "file": "src/components/services/content/WebControls.js",
1627 "id": "webControls.goHome",
1628 "start": {
1629 "column": 10,
1630 "line": 13
1631 }
1632 },
1633 {
1634 "defaultMessage": "!!!Open in Browser",
1635 "end": {
1636 "column": 3,
1637 "line": 20
1638 },
1639 "file": "src/components/services/content/WebControls.js",
1640 "id": "webControls.openInBrowser",
1641 "start": {
1642 "column": 17,
1643 "line": 17
1644 }
1645 },
1646 {
1647 "defaultMessage": "!!!Back",
1648 "end": {
1649 "column": 3,
1650 "line": 24
1651 },
1652 "file": "src/components/services/content/WebControls.js",
1653 "id": "webControls.back",
1654 "start": {
1655 "column": 8,
1656 "line": 21
1657 }
1658 },
1659 {
1660 "defaultMessage": "!!!Forward",
1661 "end": {
1662 "column": 3,
1663 "line": 28
1664 },
1665 "file": "src/components/services/content/WebControls.js",
1666 "id": "webControls.forward",
1667 "start": {
1668 "column": 11,
1669 "line": 25
1670 }
1671 },
1672 {
1673 "defaultMessage": "!!!Reload",
1674 "end": {
1675 "column": 3,
1676 "line": 32
1677 },
1678 "file": "src/components/services/content/WebControls.js",
1679 "id": "webControls.reload",
1680 "start": {
1681 "column": 10,
1682 "line": 29
1683 }
1684 }
1685 ],
1686 "path": "src/components/services/content/WebControls.json"
1687 },
1688 {
1689 "descriptors": [
1690 {
1437 "defaultMessage": "!!!Oh no!", 1691 "defaultMessage": "!!!Oh no!",
1438 "end": { 1692 "end": {
1439 "column": 3, 1693 "column": 3,
@@ -2421,107 +2675,120 @@
2421 } 2675 }
2422 }, 2676 },
2423 { 2677 {
2424 "defaultMessage": "!!!Notifications", 2678 "defaultMessage": "!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.",
2425 "end": { 2679 "end": {
2426 "column": 3, 2680 "column": 3,
2427 "line": 99 2681 "line": 99
2428 }, 2682 },
2429 "file": "src/components/settings/services/EditServiceForm.js", 2683 "file": "src/components/settings/services/EditServiceForm.js",
2684 "id": "settings.service.form.disableHibernationInfo",
2685 "start": {
2686 "column": 26,
2687 "line": 96
2688 }
2689 },
2690 {
2691 "defaultMessage": "!!!Notifications",
2692 "end": {
2693 "column": 3,
2694 "line": 103
2695 },
2696 "file": "src/components/settings/services/EditServiceForm.js",
2430 "id": "settings.service.form.headlineNotifications", 2697 "id": "settings.service.form.headlineNotifications",
2431 "start": { 2698 "start": {
2432 "column": 25, 2699 "column": 25,
2433 "line": 96 2700 "line": 100
2434 } 2701 }
2435 }, 2702 },
2436 { 2703 {
2437 "defaultMessage": "!!!Unread message badges", 2704 "defaultMessage": "!!!Unread message badges",
2438 "end": { 2705 "end": {
2439 "column": 3, 2706 "column": 3,
2440 "line": 103 2707 "line": 107
2441 }, 2708 },
2442 "file": "src/components/settings/services/EditServiceForm.js", 2709 "file": "src/components/settings/services/EditServiceForm.js",
2443 "id": "settings.service.form.headlineBadges", 2710 "id": "settings.service.form.headlineBadges",
2444 "start": { 2711 "start": {
2445 "column": 18, 2712 "column": 18,
2446 "line": 100 2713 "line": 104
2447 } 2714 }
2448 }, 2715 },
2449 { 2716 {
2450 "defaultMessage": "!!!General", 2717 "defaultMessage": "!!!General",
2451 "end": { 2718 "end": {
2452 "column": 3, 2719 "column": 3,
2453 "line": 107 2720 "line": 111
2454 }, 2721 },
2455 "file": "src/components/settings/services/EditServiceForm.js", 2722 "file": "src/components/settings/services/EditServiceForm.js",
2456 "id": "settings.service.form.headlineGeneral", 2723 "id": "settings.service.form.headlineGeneral",
2457 "start": { 2724 "start": {
2458 "column": 19, 2725 "column": 19,
2459 "line": 104 2726 "line": 108
2460 } 2727 }
2461 }, 2728 },
2462 { 2729 {
2463 "defaultMessage": "!!!Delete", 2730 "defaultMessage": "!!!Delete",
2464 "end": { 2731 "end": {
2465 "column": 3, 2732 "column": 3,
2466 "line": 111 2733 "line": 115
2467 }, 2734 },
2468 "file": "src/components/settings/services/EditServiceForm.js", 2735 "file": "src/components/settings/services/EditServiceForm.js",
2469 "id": "settings.service.form.iconDelete", 2736 "id": "settings.service.form.iconDelete",
2470 "start": { 2737 "start": {
2471 "column": 14, 2738 "column": 14,
2472 "line": 108 2739 "line": 112
2473 } 2740 }
2474 }, 2741 },
2475 { 2742 {
2476 "defaultMessage": "!!!Drop your image, or click here", 2743 "defaultMessage": "!!!Drop your image, or click here",
2477 "end": { 2744 "end": {
2478 "column": 3, 2745 "column": 3,
2479 "line": 115 2746 "line": 119
2480 }, 2747 },
2481 "file": "src/components/settings/services/EditServiceForm.js", 2748 "file": "src/components/settings/services/EditServiceForm.js",
2482 "id": "settings.service.form.iconUpload", 2749 "id": "settings.service.form.iconUpload",
2483 "start": { 2750 "start": {
2484 "column": 14, 2751 "column": 14,
2485 "line": 112 2752 "line": 116
2486 } 2753 }
2487 }, 2754 },
2488 { 2755 {
2489 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 2756 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
2490 "end": { 2757 "end": {
2491 "column": 3, 2758 "column": 3,
2492 "line": 119 2759 "line": 123
2493 }, 2760 },
2494 "file": "src/components/settings/services/EditServiceForm.js", 2761 "file": "src/components/settings/services/EditServiceForm.js",
2495 "id": "settings.service.form.proxy.headline", 2762 "id": "settings.service.form.proxy.headline",
2496 "start": { 2763 "start": {
2497 "column": 17, 2764 "column": 17,
2498 "line": 116 2765 "line": 120
2499 } 2766 }
2500 }, 2767 },
2501 { 2768 {
2502 "defaultMessage": "!!!Please restart Ferdi after changing proxy Settings.", 2769 "defaultMessage": "!!!Please restart Ferdi after changing proxy Settings.",
2503 "end": { 2770 "end": {
2504 "column": 3, 2771 "column": 3,
2505 "line": 123 2772 "line": 127
2506 }, 2773 },
2507 "file": "src/components/settings/services/EditServiceForm.js", 2774 "file": "src/components/settings/services/EditServiceForm.js",
2508 "id": "settings.service.form.proxy.restartInfo", 2775 "id": "settings.service.form.proxy.restartInfo",
2509 "start": { 2776 "start": {
2510 "column": 20, 2777 "column": 20,
2511 "line": 120 2778 "line": 124
2512 } 2779 }
2513 }, 2780 },
2514 { 2781 {
2515 "defaultMessage": "!!!Proxy settings will not be synchronized with the Ferdi servers.", 2782 "defaultMessage": "!!!Proxy settings will not be synchronized with the Ferdi servers.",
2516 "end": { 2783 "end": {
2517 "column": 3, 2784 "column": 3,
2518 "line": 127 2785 "line": 131
2519 }, 2786 },
2520 "file": "src/components/settings/services/EditServiceForm.js", 2787 "file": "src/components/settings/services/EditServiceForm.js",
2521 "id": "settings.service.form.proxy.info", 2788 "id": "settings.service.form.proxy.info",
2522 "start": { 2789 "start": {
2523 "column": 13, 2790 "column": 13,
2524 "line": 124 2791 "line": 128
2525 } 2792 }
2526 } 2793 }
2527 ], 2794 ],
@@ -3888,6 +4155,19 @@
3888 "column": 10, 4155 "column": 10,
3889 "line": 65 4156 "line": 65
3890 } 4157 }
4158 },
4159 {
4160 "defaultMessage": "!!!Occasional Waiting Screens",
4161 "end": {
4162 "column": 3,
4163 "line": 72
4164 },
4165 "file": "src/components/ui/FeatureList.js",
4166 "id": "pricing.features.appDelaysEnabled",
4167 "start": {
4168 "column": 19,
4169 "line": 69
4170 }
3891 } 4171 }
3892 ], 4172 ],
3893 "path": "src/components/ui/FeatureList.json" 4173 "path": "src/components/ui/FeatureList.json"
@@ -4006,172 +4286,185 @@
4006 } 4286 }
4007 }, 4287 },
4008 { 4288 {
4009 "defaultMessage": "!!!Enable Notifications", 4289 "defaultMessage": "!!!Disable hibernation",
4010 "end": { 4290 "end": {
4011 "column": 3, 4291 "column": 3,
4012 "line": 39 4292 "line": 39
4013 }, 4293 },
4014 "file": "src/containers/settings/EditServiceScreen.js", 4294 "file": "src/containers/settings/EditServiceScreen.js",
4015 "id": "settings.service.form.enableNotification", 4295 "id": "settings.service.form.disableHibernation",
4016 "start": { 4296 "start": {
4017 "column": 22, 4297 "column": 22,
4018 "line": 36 4298 "line": 36
4019 } 4299 }
4020 }, 4300 },
4021 { 4301 {
4022 "defaultMessage": "!!!Show unread message badges", 4302 "defaultMessage": "!!!Enable Notifications",
4023 "end": { 4303 "end": {
4024 "column": 3, 4304 "column": 3,
4025 "line": 43 4305 "line": 43
4026 }, 4306 },
4027 "file": "src/containers/settings/EditServiceScreen.js", 4307 "file": "src/containers/settings/EditServiceScreen.js",
4308 "id": "settings.service.form.enableNotification",
4309 "start": {
4310 "column": 22,
4311 "line": 40
4312 }
4313 },
4314 {
4315 "defaultMessage": "!!!Show unread message badges",
4316 "end": {
4317 "column": 3,
4318 "line": 47
4319 },
4320 "file": "src/containers/settings/EditServiceScreen.js",
4028 "id": "settings.service.form.enableBadge", 4321 "id": "settings.service.form.enableBadge",
4029 "start": { 4322 "start": {
4030 "column": 15, 4323 "column": 15,
4031 "line": 40 4324 "line": 44
4032 } 4325 }
4033 }, 4326 },
4034 { 4327 {
4035 "defaultMessage": "!!!Enable audio", 4328 "defaultMessage": "!!!Enable audio",
4036 "end": { 4329 "end": {
4037 "column": 3, 4330 "column": 3,
4038 "line": 47 4331 "line": 51
4039 }, 4332 },
4040 "file": "src/containers/settings/EditServiceScreen.js", 4333 "file": "src/containers/settings/EditServiceScreen.js",
4041 "id": "settings.service.form.enableAudio", 4334 "id": "settings.service.form.enableAudio",
4042 "start": { 4335 "start": {
4043 "column": 15, 4336 "column": 15,
4044 "line": 44 4337 "line": 48
4045 } 4338 }
4046 }, 4339 },
4047 { 4340 {
4048 "defaultMessage": "!!!Team", 4341 "defaultMessage": "!!!Team",
4049 "end": { 4342 "end": {
4050 "column": 3, 4343 "column": 3,
4051 "line": 51 4344 "line": 55
4052 }, 4345 },
4053 "file": "src/containers/settings/EditServiceScreen.js", 4346 "file": "src/containers/settings/EditServiceScreen.js",
4054 "id": "settings.service.form.team", 4347 "id": "settings.service.form.team",
4055 "start": { 4348 "start": {
4056 "column": 8, 4349 "column": 8,
4057 "line": 48 4350 "line": 52
4058 } 4351 }
4059 }, 4352 },
4060 { 4353 {
4061 "defaultMessage": "!!!Service URL", 4354 "defaultMessage": "!!!Service URL",
4062 "end": { 4355 "end": {
4063 "column": 3, 4356 "column": 3,
4064 "line": 55 4357 "line": 59
4065 }, 4358 },
4066 "file": "src/containers/settings/EditServiceScreen.js", 4359 "file": "src/containers/settings/EditServiceScreen.js",
4067 "id": "settings.service.form.customUrl", 4360 "id": "settings.service.form.customUrl",
4068 "start": { 4361 "start": {
4069 "column": 13, 4362 "column": 13,
4070 "line": 52 4363 "line": 56
4071 } 4364 }
4072 }, 4365 },
4073 { 4366 {
4074 "defaultMessage": "!!!Show message badge for all new messages", 4367 "defaultMessage": "!!!Show message badge for all new messages",
4075 "end": { 4368 "end": {
4076 "column": 3, 4369 "column": 3,
4077 "line": 59 4370 "line": 63
4078 }, 4371 },
4079 "file": "src/containers/settings/EditServiceScreen.js", 4372 "file": "src/containers/settings/EditServiceScreen.js",
4080 "id": "settings.service.form.indirectMessages", 4373 "id": "settings.service.form.indirectMessages",
4081 "start": { 4374 "start": {
4082 "column": 20, 4375 "column": 20,
4083 "line": 56 4376 "line": 60
4084 } 4377 }
4085 }, 4378 },
4086 { 4379 {
4087 "defaultMessage": "!!!Custom icon", 4380 "defaultMessage": "!!!Custom icon",
4088 "end": { 4381 "end": {
4089 "column": 3, 4382 "column": 3,
4090 "line": 63 4383 "line": 67
4091 }, 4384 },
4092 "file": "src/containers/settings/EditServiceScreen.js", 4385 "file": "src/containers/settings/EditServiceScreen.js",
4093 "id": "settings.service.form.icon", 4386 "id": "settings.service.form.icon",
4094 "start": { 4387 "start": {
4095 "column": 8, 4388 "column": 8,
4096 "line": 60 4389 "line": 64
4097 } 4390 }
4098 }, 4391 },
4099 { 4392 {
4100 "defaultMessage": "!!!Enable Dark Mode", 4393 "defaultMessage": "!!!Enable Dark Mode",
4101 "end": { 4394 "end": {
4102 "column": 3, 4395 "column": 3,
4103 "line": 67 4396 "line": 71
4104 }, 4397 },
4105 "file": "src/containers/settings/EditServiceScreen.js", 4398 "file": "src/containers/settings/EditServiceScreen.js",
4106 "id": "settings.service.form.enableDarkMode", 4399 "id": "settings.service.form.enableDarkMode",
4107 "start": { 4400 "start": {
4108 "column": 18, 4401 "column": 18,
4109 "line": 64 4402 "line": 68
4110 } 4403 }
4111 }, 4404 },
4112 { 4405 {
4113 "defaultMessage": "!!!Use Proxy", 4406 "defaultMessage": "!!!Use Proxy",
4114 "end": { 4407 "end": {
4115 "column": 3, 4408 "column": 3,
4116 "line": 71 4409 "line": 75
4117 }, 4410 },
4118 "file": "src/containers/settings/EditServiceScreen.js", 4411 "file": "src/containers/settings/EditServiceScreen.js",
4119 "id": "settings.service.form.proxy.isEnabled", 4412 "id": "settings.service.form.proxy.isEnabled",
4120 "start": { 4413 "start": {
4121 "column": 15, 4414 "column": 15,
4122 "line": 68 4415 "line": 72
4123 } 4416 }
4124 }, 4417 },
4125 { 4418 {
4126 "defaultMessage": "!!!Proxy Host/IP", 4419 "defaultMessage": "!!!Proxy Host/IP",
4127 "end": { 4420 "end": {
4128 "column": 3, 4421 "column": 3,
4129 "line": 75 4422 "line": 79
4130 }, 4423 },
4131 "file": "src/containers/settings/EditServiceScreen.js", 4424 "file": "src/containers/settings/EditServiceScreen.js",
4132 "id": "settings.service.form.proxy.host", 4425 "id": "settings.service.form.proxy.host",
4133 "start": { 4426 "start": {
4134 "column": 13, 4427 "column": 13,
4135 "line": 72 4428 "line": 76
4136 } 4429 }
4137 }, 4430 },
4138 { 4431 {
4139 "defaultMessage": "!!!Port", 4432 "defaultMessage": "!!!Port",
4140 "end": { 4433 "end": {
4141 "column": 3, 4434 "column": 3,
4142 "line": 79 4435 "line": 83
4143 }, 4436 },
4144 "file": "src/containers/settings/EditServiceScreen.js", 4437 "file": "src/containers/settings/EditServiceScreen.js",
4145 "id": "settings.service.form.proxy.port", 4438 "id": "settings.service.form.proxy.port",
4146 "start": { 4439 "start": {
4147 "column": 13, 4440 "column": 13,
4148 "line": 76 4441 "line": 80
4149 } 4442 }
4150 }, 4443 },
4151 { 4444 {
4152 "defaultMessage": "!!!User", 4445 "defaultMessage": "!!!User",
4153 "end": { 4446 "end": {
4154 "column": 3, 4447 "column": 3,
4155 "line": 83 4448 "line": 87
4156 }, 4449 },
4157 "file": "src/containers/settings/EditServiceScreen.js", 4450 "file": "src/containers/settings/EditServiceScreen.js",
4158 "id": "settings.service.form.proxy.user", 4451 "id": "settings.service.form.proxy.user",
4159 "start": { 4452 "start": {
4160 "column": 13, 4453 "column": 13,
4161 "line": 80 4454 "line": 84
4162 } 4455 }
4163 }, 4456 },
4164 { 4457 {
4165 "defaultMessage": "!!!Password", 4458 "defaultMessage": "!!!Password",
4166 "end": { 4459 "end": {
4167 "column": 3, 4460 "column": 3,
4168 "line": 87 4461 "line": 91
4169 }, 4462 },
4170 "file": "src/containers/settings/EditServiceScreen.js", 4463 "file": "src/containers/settings/EditServiceScreen.js",
4171 "id": "settings.service.form.proxy.password", 4464 "id": "settings.service.form.proxy.password",
4172 "start": { 4465 "start": {
4173 "column": 17, 4466 "column": 17,
4174 "line": 84 4467 "line": 88
4175 } 4468 }
4176 } 4469 }
4177 ], 4470 ],
@@ -5327,6 +5620,76 @@
5327 { 5620 {
5328 "descriptors": [ 5621 "descriptors": [
5329 { 5622 {
5623 "defaultMessage": "!!!Home",
5624 "end": {
5625 "column": 3,
5626 "line": 16
5627 },
5628 "file": "src/features/recipeConnectionLost/components/WebControls.js",
5629 "id": "webControls.goHome",
5630 "start": {
5631 "column": 10,
5632 "line": 13
5633 }
5634 },
5635 {
5636 "defaultMessage": "!!!Open in Browser",
5637 "end": {
5638 "column": 3,
5639 "line": 20
5640 },
5641 "file": "src/features/recipeConnectionLost/components/WebControls.js",
5642 "id": "webControls.openInBrowser",
5643 "start": {
5644 "column": 17,
5645 "line": 17
5646 }
5647 },
5648 {
5649 "defaultMessage": "!!!Back",
5650 "end": {
5651 "column": 3,
5652 "line": 24
5653 },
5654 "file": "src/features/recipeConnectionLost/components/WebControls.js",
5655 "id": "webControls.back",
5656 "start": {
5657 "column": 8,
5658 "line": 21
5659 }
5660 },
5661 {
5662 "defaultMessage": "!!!Forward",
5663 "end": {
5664 "column": 3,
5665 "line": 28
5666 },
5667 "file": "src/features/recipeConnectionLost/components/WebControls.js",
5668 "id": "webControls.forward",
5669 "start": {
5670 "column": 11,
5671 "line": 25
5672 }
5673 },
5674 {
5675 "defaultMessage": "!!!Reload",
5676 "end": {
5677 "column": 3,
5678 "line": 32
5679 },
5680 "file": "src/features/recipeConnectionLost/components/WebControls.js",
5681 "id": "webControls.reload",
5682 "start": {
5683 "column": 10,
5684 "line": 29
5685 }
5686 }
5687 ],
5688 "path": "src/features/recipeConnectionLost/components/WebControls.json"
5689 },
5690 {
5691 "descriptors": [
5692 {
5330 "defaultMessage": "!!!Changes in Franz {version}", 5693 "defaultMessage": "!!!Changes in Franz {version}",
5331 "end": { 5694 "end": {
5332 "column": 3, 5695 "column": 3,
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 947f927e7..8a95970a2 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -4,6 +4,9 @@
4 "changeserver.headline": "Change server", 4 "changeserver.headline": "Change server",
5 "changeserver.label": "Server", 5 "changeserver.label": "Server",
6 "changeserver.submit": "Submit", 6 "changeserver.submit": "Submit",
7 "connectionLostBanner.cta": "Reload Service",
8 "connectionLostBanner.informationLink": "What happened?",
9 "connectionLostBanner.message": "Oh no! Ferdi lost the connection to {name}.",
7 "feature.announcements.changelog.headline": "Changes in Ferdi {version}", 10 "feature.announcements.changelog.headline": "Changes in Ferdi {version}",
8 "feature.debugger.title": "Publish debugging information", 11 "feature.debugger.title": "Publish debugging information",
9 "feature.delayApp.headline": "Please purchase a Ferdi Supporter License to skip waiting", 12 "feature.delayApp.headline": "Please purchase a Ferdi Supporter License to skip waiting",
@@ -183,6 +186,7 @@
183 "pricing.features.accountSync": "Account Synchronisation", 186 "pricing.features.accountSync": "Account Synchronisation",
184 "pricing.features.adFree": "Forever ad-free", 187 "pricing.features.adFree": "Forever ad-free",
185 "pricing.features.appDelays": "No Waiting Screens", 188 "pricing.features.appDelays": "No Waiting Screens",
189 "pricing.features.appDelaysEnabled": "Occasional Waiting Screens",
186 "pricing.features.customWebsites": "Add Custom Websites", 190 "pricing.features.customWebsites": "Add Custom Websites",
187 "pricing.features.desktopNotifications": "Desktop Notifications", 191 "pricing.features.desktopNotifications": "Desktop Notifications",
188 "pricing.features.onPremise": "On-premise & other Hosted Services", 192 "pricing.features.onPremise": "On-premise & other Hosted Services",
@@ -305,8 +309,8 @@
305 "settings.app.form.sentry": "Send telemetry data", 309 "settings.app.form.sentry": "Send telemetry data",
306 "settings.app.form.serviceRibbonWidth": "Sidebar width", 310 "settings.app.form.serviceRibbonWidth": "Sidebar width",
307 "settings.app.form.showDisabledServices": "Display disabled services tabs", 311 "settings.app.form.showDisabledServices": "Display disabled services tabs",
308 "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled",
309 "settings.app.form.showDragArea": "Show draggable area on window", 312 "settings.app.form.showDragArea": "Show draggable area on window",
313 "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled",
310 "settings.app.form.startMinimized": "Start minimized", 314 "settings.app.form.startMinimized": "Start minimized",
311 "settings.app.form.universalDarkMode": "Enable universal Dark Mode", 315 "settings.app.form.universalDarkMode": "Enable universal Dark Mode",
312 "settings.app.form.useTouchIdToUnlock": "Allow using TouchID to unlock Ferdi", 316 "settings.app.form.useTouchIdToUnlock": "Allow using TouchID to unlock Ferdi",
@@ -366,6 +370,8 @@
366 "settings.service.form.customUrlUpgradeAccount": "Upgrade your account", 370 "settings.service.form.customUrlUpgradeAccount": "Upgrade your account",
367 "settings.service.form.customUrlValidationError": "Could not validate custom {name} server.", 371 "settings.service.form.customUrlValidationError": "Could not validate custom {name} server.",
368 "settings.service.form.deleteButton": "Delete service", 372 "settings.service.form.deleteButton": "Delete service",
373 "settings.service.form.disableHibernation": "Disable hibernation",
374 "settings.service.form.disableHibernationInfo": "You currently have hibernation enabled but you can disable hibernation for individual services using this option.",
369 "settings.service.form.editServiceHeadline": "Edit {name}", 375 "settings.service.form.editServiceHeadline": "Edit {name}",
370 "settings.service.form.enableAudio": "Enable audio", 376 "settings.service.form.enableAudio": "Enable audio",
371 "settings.service.form.enableBadge": "Show unread message badges", 377 "settings.service.form.enableBadge": "Show unread message badges",
diff --git a/src/i18n/messages/src/components/services/content/ConnectionBanner.json b/src/i18n/messages/src/components/services/content/ConnectionBanner.json
new file mode 100644
index 000000000..1047c28b5
--- /dev/null
+++ b/src/i18n/messages/src/components/services/content/ConnectionBanner.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "webControls.goHome",
4 "defaultMessage": "!!!Home",
5 "file": "src/components/services/content/ConnectionBanner.js",
6 "start": {
7 "line": 13,
8 "column": 10
9 },
10 "end": {
11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "webControls.openInBrowser",
17 "defaultMessage": "!!!Open in Browser",
18 "file": "src/components/services/content/ConnectionBanner.js",
19 "start": {
20 "line": 17,
21 "column": 17
22 },
23 "end": {
24 "line": 20,
25 "column": 3
26 }
27 },
28 {
29 "id": "webControls.back",
30 "defaultMessage": "!!!Back",
31 "file": "src/components/services/content/ConnectionBanner.js",
32 "start": {
33 "line": 21,
34 "column": 8
35 },
36 "end": {
37 "line": 24,
38 "column": 3
39 }
40 },
41 {
42 "id": "webControls.forward",
43 "defaultMessage": "!!!Forward",
44 "file": "src/components/services/content/ConnectionBanner.js",
45 "start": {
46 "line": 25,
47 "column": 11
48 },
49 "end": {
50 "line": 28,
51 "column": 3
52 }
53 },
54 {
55 "id": "webControls.reload",
56 "defaultMessage": "!!!Reload",
57 "file": "src/components/services/content/ConnectionBanner.js",
58 "start": {
59 "line": 29,
60 "column": 10
61 },
62 "end": {
63 "line": 32,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/services/content/ConnectionLost.json b/src/i18n/messages/src/components/services/content/ConnectionLost.json
new file mode 100644
index 000000000..ee3f7a4ba
--- /dev/null
+++ b/src/i18n/messages/src/components/services/content/ConnectionLost.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "webControls.goHome",
4 "defaultMessage": "!!!Home",
5 "file": "src/components/services/content/ConnectionLost.js",
6 "start": {
7 "line": 13,
8 "column": 10
9 },
10 "end": {
11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "webControls.openInBrowser",
17 "defaultMessage": "!!!Open in Browser",
18 "file": "src/components/services/content/ConnectionLost.js",
19 "start": {
20 "line": 17,
21 "column": 17
22 },
23 "end": {
24 "line": 20,
25 "column": 3
26 }
27 },
28 {
29 "id": "webControls.back",
30 "defaultMessage": "!!!Back",
31 "file": "src/components/services/content/ConnectionLost.js",
32 "start": {
33 "line": 21,
34 "column": 8
35 },
36 "end": {
37 "line": 24,
38 "column": 3
39 }
40 },
41 {
42 "id": "webControls.forward",
43 "defaultMessage": "!!!Forward",
44 "file": "src/components/services/content/ConnectionLost.js",
45 "start": {
46 "line": 25,
47 "column": 11
48 },
49 "end": {
50 "line": 28,
51 "column": 3
52 }
53 },
54 {
55 "id": "webControls.reload",
56 "defaultMessage": "!!!Reload",
57 "file": "src/components/services/content/ConnectionLost.js",
58 "start": {
59 "line": 29,
60 "column": 10
61 },
62 "end": {
63 "line": 32,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/services/content/ConnectionLostBanner.json b/src/i18n/messages/src/components/services/content/ConnectionLostBanner.json
new file mode 100644
index 000000000..fd7019f41
--- /dev/null
+++ b/src/i18n/messages/src/components/services/content/ConnectionLostBanner.json
@@ -0,0 +1,41 @@
1[
2 {
3 "id": "connectionLostBanner.message",
4 "defaultMessage": "!!!Oh no! Franz lost the connection to {name}.",
5 "file": "src/components/services/content/ConnectionLostBanner.js",
6 "start": {
7 "line": 15,
8 "column": 8
9 },
10 "end": {
11 "line": 18,
12 "column": 3
13 }
14 },
15 {
16 "id": "connectionLostBanner.informationLink",
17 "defaultMessage": "!!!What happened?",
18 "file": "src/components/services/content/ConnectionLostBanner.js",
19 "start": {
20 "line": 19,
21 "column": 19
22 },
23 "end": {
24 "line": 22,
25 "column": 3
26 }
27 },
28 {
29 "id": "connectionLostBanner.cta",
30 "defaultMessage": "!!!Reload Service",
31 "file": "src/components/services/content/ConnectionLostBanner.js",
32 "start": {
33 "line": 23,
34 "column": 7
35 },
36 "end": {
37 "line": 26,
38 "column": 3
39 }
40 }
41] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/services/content/WebControls.json b/src/i18n/messages/src/components/services/content/WebControls.json
new file mode 100644
index 000000000..5af5143d0
--- /dev/null
+++ b/src/i18n/messages/src/components/services/content/WebControls.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "webControls.goHome",
4 "defaultMessage": "!!!Home",
5 "file": "src/components/services/content/WebControls.js",
6 "start": {
7 "line": 13,
8 "column": 10
9 },
10 "end": {
11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "webControls.openInBrowser",
17 "defaultMessage": "!!!Open in Browser",
18 "file": "src/components/services/content/WebControls.js",
19 "start": {
20 "line": 17,
21 "column": 17
22 },
23 "end": {
24 "line": 20,
25 "column": 3
26 }
27 },
28 {
29 "id": "webControls.back",
30 "defaultMessage": "!!!Back",
31 "file": "src/components/services/content/WebControls.js",
32 "start": {
33 "line": 21,
34 "column": 8
35 },
36 "end": {
37 "line": 24,
38 "column": 3
39 }
40 },
41 {
42 "id": "webControls.forward",
43 "defaultMessage": "!!!Forward",
44 "file": "src/components/services/content/WebControls.js",
45 "start": {
46 "line": 25,
47 "column": 11
48 },
49 "end": {
50 "line": 28,
51 "column": 3
52 }
53 },
54 {
55 "id": "webControls.reload",
56 "defaultMessage": "!!!Reload",
57 "file": "src/components/services/content/WebControls.js",
58 "start": {
59 "line": 29,
60 "column": 10
61 },
62 "end": {
63 "line": 32,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/settings/services/EditServiceForm.json b/src/i18n/messages/src/components/settings/services/EditServiceForm.json
index df64c8a5f..811c49498 100644
--- a/src/i18n/messages/src/components/settings/services/EditServiceForm.json
+++ b/src/i18n/messages/src/components/settings/services/EditServiceForm.json
@@ -234,15 +234,28 @@
234 } 234 }
235 }, 235 },
236 { 236 {
237 "id": "settings.service.form.disableHibernationInfo",
238 "defaultMessage": "!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.",
239 "file": "src/components/settings/services/EditServiceForm.js",
240 "start": {
241 "line": 96,
242 "column": 26
243 },
244 "end": {
245 "line": 99,
246 "column": 3
247 }
248 },
249 {
237 "id": "settings.service.form.headlineNotifications", 250 "id": "settings.service.form.headlineNotifications",
238 "defaultMessage": "!!!Notifications", 251 "defaultMessage": "!!!Notifications",
239 "file": "src/components/settings/services/EditServiceForm.js", 252 "file": "src/components/settings/services/EditServiceForm.js",
240 "start": { 253 "start": {
241 "line": 96, 254 "line": 100,
242 "column": 25 255 "column": 25
243 }, 256 },
244 "end": { 257 "end": {
245 "line": 99, 258 "line": 103,
246 "column": 3 259 "column": 3
247 } 260 }
248 }, 261 },
@@ -251,11 +264,11 @@
251 "defaultMessage": "!!!Unread message badges", 264 "defaultMessage": "!!!Unread message badges",
252 "file": "src/components/settings/services/EditServiceForm.js", 265 "file": "src/components/settings/services/EditServiceForm.js",
253 "start": { 266 "start": {
254 "line": 100, 267 "line": 104,
255 "column": 18 268 "column": 18
256 }, 269 },
257 "end": { 270 "end": {
258 "line": 103, 271 "line": 107,
259 "column": 3 272 "column": 3
260 } 273 }
261 }, 274 },
@@ -264,11 +277,11 @@
264 "defaultMessage": "!!!General", 277 "defaultMessage": "!!!General",
265 "file": "src/components/settings/services/EditServiceForm.js", 278 "file": "src/components/settings/services/EditServiceForm.js",
266 "start": { 279 "start": {
267 "line": 104, 280 "line": 108,
268 "column": 19 281 "column": 19
269 }, 282 },
270 "end": { 283 "end": {
271 "line": 107, 284 "line": 111,
272 "column": 3 285 "column": 3
273 } 286 }
274 }, 287 },
@@ -277,11 +290,11 @@
277 "defaultMessage": "!!!Delete", 290 "defaultMessage": "!!!Delete",
278 "file": "src/components/settings/services/EditServiceForm.js", 291 "file": "src/components/settings/services/EditServiceForm.js",
279 "start": { 292 "start": {
280 "line": 108, 293 "line": 112,
281 "column": 14 294 "column": 14
282 }, 295 },
283 "end": { 296 "end": {
284 "line": 111, 297 "line": 115,
285 "column": 3 298 "column": 3
286 } 299 }
287 }, 300 },
@@ -290,11 +303,11 @@
290 "defaultMessage": "!!!Drop your image, or click here", 303 "defaultMessage": "!!!Drop your image, or click here",
291 "file": "src/components/settings/services/EditServiceForm.js", 304 "file": "src/components/settings/services/EditServiceForm.js",
292 "start": { 305 "start": {
293 "line": 112, 306 "line": 116,
294 "column": 14 307 "column": 14
295 }, 308 },
296 "end": { 309 "end": {
297 "line": 115, 310 "line": 119,
298 "column": 3 311 "column": 3
299 } 312 }
300 }, 313 },
@@ -303,11 +316,11 @@
303 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 316 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
304 "file": "src/components/settings/services/EditServiceForm.js", 317 "file": "src/components/settings/services/EditServiceForm.js",
305 "start": { 318 "start": {
306 "line": 116, 319 "line": 120,
307 "column": 17 320 "column": 17
308 }, 321 },
309 "end": { 322 "end": {
310 "line": 119, 323 "line": 123,
311 "column": 3 324 "column": 3
312 } 325 }
313 }, 326 },
@@ -316,11 +329,11 @@
316 "defaultMessage": "!!!Please restart Ferdi after changing proxy Settings.", 329 "defaultMessage": "!!!Please restart Ferdi after changing proxy Settings.",
317 "file": "src/components/settings/services/EditServiceForm.js", 330 "file": "src/components/settings/services/EditServiceForm.js",
318 "start": { 331 "start": {
319 "line": 120, 332 "line": 124,
320 "column": 20 333 "column": 20
321 }, 334 },
322 "end": { 335 "end": {
323 "line": 123, 336 "line": 127,
324 "column": 3 337 "column": 3
325 } 338 }
326 }, 339 },
@@ -329,11 +342,11 @@
329 "defaultMessage": "!!!Proxy settings will not be synchronized with the Ferdi servers.", 342 "defaultMessage": "!!!Proxy settings will not be synchronized with the Ferdi servers.",
330 "file": "src/components/settings/services/EditServiceForm.js", 343 "file": "src/components/settings/services/EditServiceForm.js",
331 "start": { 344 "start": {
332 "line": 124, 345 "line": 128,
333 "column": 13 346 "column": 13
334 }, 347 },
335 "end": { 348 "end": {
336 "line": 127, 349 "line": 131,
337 "column": 3 350 "column": 3
338 } 351 }
339 } 352 }
diff --git a/src/i18n/messages/src/components/ui/FeatureList.json b/src/i18n/messages/src/components/ui/FeatureList.json
index 3201115b3..8d1dc4360 100644
--- a/src/i18n/messages/src/components/ui/FeatureList.json
+++ b/src/i18n/messages/src/components/ui/FeatureList.json
@@ -193,5 +193,18 @@
193 "line": 68, 193 "line": 68,
194 "column": 3 194 "column": 3
195 } 195 }
196 },
197 {
198 "id": "pricing.features.appDelaysEnabled",
199 "defaultMessage": "!!!Occasional Waiting Screens",
200 "file": "src/components/ui/FeatureList.js",
201 "start": {
202 "line": 69,
203 "column": 19
204 },
205 "end": {
206 "line": 72,
207 "column": 3
208 }
196 } 209 }
197] \ No newline at end of file 210] \ No newline at end of file
diff --git a/src/i18n/messages/src/containers/settings/EditServiceScreen.json b/src/i18n/messages/src/containers/settings/EditServiceScreen.json
index 9b46a1e6f..36937ebf8 100644
--- a/src/i18n/messages/src/containers/settings/EditServiceScreen.json
+++ b/src/i18n/messages/src/containers/settings/EditServiceScreen.json
@@ -26,8 +26,8 @@
26 } 26 }
27 }, 27 },
28 { 28 {
29 "id": "settings.service.form.enableNotification", 29 "id": "settings.service.form.disableHibernation",
30 "defaultMessage": "!!!Enable Notifications", 30 "defaultMessage": "!!!Disable hibernation",
31 "file": "src/containers/settings/EditServiceScreen.js", 31 "file": "src/containers/settings/EditServiceScreen.js",
32 "start": { 32 "start": {
33 "line": 36, 33 "line": 36,
@@ -39,15 +39,28 @@
39 } 39 }
40 }, 40 },
41 { 41 {
42 "id": "settings.service.form.enableNotification",
43 "defaultMessage": "!!!Enable Notifications",
44 "file": "src/containers/settings/EditServiceScreen.js",
45 "start": {
46 "line": 40,
47 "column": 22
48 },
49 "end": {
50 "line": 43,
51 "column": 3
52 }
53 },
54 {
42 "id": "settings.service.form.enableBadge", 55 "id": "settings.service.form.enableBadge",
43 "defaultMessage": "!!!Show unread message badges", 56 "defaultMessage": "!!!Show unread message badges",
44 "file": "src/containers/settings/EditServiceScreen.js", 57 "file": "src/containers/settings/EditServiceScreen.js",
45 "start": { 58 "start": {
46 "line": 40, 59 "line": 44,
47 "column": 15 60 "column": 15
48 }, 61 },
49 "end": { 62 "end": {
50 "line": 43, 63 "line": 47,
51 "column": 3 64 "column": 3
52 } 65 }
53 }, 66 },
@@ -56,11 +69,11 @@
56 "defaultMessage": "!!!Enable audio", 69 "defaultMessage": "!!!Enable audio",
57 "file": "src/containers/settings/EditServiceScreen.js", 70 "file": "src/containers/settings/EditServiceScreen.js",
58 "start": { 71 "start": {
59 "line": 44, 72 "line": 48,
60 "column": 15 73 "column": 15
61 }, 74 },
62 "end": { 75 "end": {
63 "line": 47, 76 "line": 51,
64 "column": 3 77 "column": 3
65 } 78 }
66 }, 79 },
@@ -69,11 +82,11 @@
69 "defaultMessage": "!!!Team", 82 "defaultMessage": "!!!Team",
70 "file": "src/containers/settings/EditServiceScreen.js", 83 "file": "src/containers/settings/EditServiceScreen.js",
71 "start": { 84 "start": {
72 "line": 48, 85 "line": 52,
73 "column": 8 86 "column": 8
74 }, 87 },
75 "end": { 88 "end": {
76 "line": 51, 89 "line": 55,
77 "column": 3 90 "column": 3
78 } 91 }
79 }, 92 },
@@ -82,11 +95,11 @@
82 "defaultMessage": "!!!Service URL", 95 "defaultMessage": "!!!Service URL",
83 "file": "src/containers/settings/EditServiceScreen.js", 96 "file": "src/containers/settings/EditServiceScreen.js",
84 "start": { 97 "start": {
85 "line": 52, 98 "line": 56,
86 "column": 13 99 "column": 13
87 }, 100 },
88 "end": { 101 "end": {
89 "line": 55, 102 "line": 59,
90 "column": 3 103 "column": 3
91 } 104 }
92 }, 105 },
@@ -95,11 +108,11 @@
95 "defaultMessage": "!!!Show message badge for all new messages", 108 "defaultMessage": "!!!Show message badge for all new messages",
96 "file": "src/containers/settings/EditServiceScreen.js", 109 "file": "src/containers/settings/EditServiceScreen.js",
97 "start": { 110 "start": {
98 "line": 56, 111 "line": 60,
99 "column": 20 112 "column": 20
100 }, 113 },
101 "end": { 114 "end": {
102 "line": 59, 115 "line": 63,
103 "column": 3 116 "column": 3
104 } 117 }
105 }, 118 },
@@ -108,11 +121,11 @@
108 "defaultMessage": "!!!Custom icon", 121 "defaultMessage": "!!!Custom icon",
109 "file": "src/containers/settings/EditServiceScreen.js", 122 "file": "src/containers/settings/EditServiceScreen.js",
110 "start": { 123 "start": {
111 "line": 60, 124 "line": 64,
112 "column": 8 125 "column": 8
113 }, 126 },
114 "end": { 127 "end": {
115 "line": 63, 128 "line": 67,
116 "column": 3 129 "column": 3
117 } 130 }
118 }, 131 },
@@ -121,11 +134,11 @@
121 "defaultMessage": "!!!Enable Dark Mode", 134 "defaultMessage": "!!!Enable Dark Mode",
122 "file": "src/containers/settings/EditServiceScreen.js", 135 "file": "src/containers/settings/EditServiceScreen.js",
123 "start": { 136 "start": {
124 "line": 64, 137 "line": 68,
125 "column": 18 138 "column": 18
126 }, 139 },
127 "end": { 140 "end": {
128 "line": 67, 141 "line": 71,
129 "column": 3 142 "column": 3
130 } 143 }
131 }, 144 },
@@ -134,11 +147,11 @@
134 "defaultMessage": "!!!Use Proxy", 147 "defaultMessage": "!!!Use Proxy",
135 "file": "src/containers/settings/EditServiceScreen.js", 148 "file": "src/containers/settings/EditServiceScreen.js",
136 "start": { 149 "start": {
137 "line": 68, 150 "line": 72,
138 "column": 15 151 "column": 15
139 }, 152 },
140 "end": { 153 "end": {
141 "line": 71, 154 "line": 75,
142 "column": 3 155 "column": 3
143 } 156 }
144 }, 157 },
@@ -147,11 +160,11 @@
147 "defaultMessage": "!!!Proxy Host/IP", 160 "defaultMessage": "!!!Proxy Host/IP",
148 "file": "src/containers/settings/EditServiceScreen.js", 161 "file": "src/containers/settings/EditServiceScreen.js",
149 "start": { 162 "start": {
150 "line": 72, 163 "line": 76,
151 "column": 13 164 "column": 13
152 }, 165 },
153 "end": { 166 "end": {
154 "line": 75, 167 "line": 79,
155 "column": 3 168 "column": 3
156 } 169 }
157 }, 170 },
@@ -160,11 +173,11 @@
160 "defaultMessage": "!!!Port", 173 "defaultMessage": "!!!Port",
161 "file": "src/containers/settings/EditServiceScreen.js", 174 "file": "src/containers/settings/EditServiceScreen.js",
162 "start": { 175 "start": {
163 "line": 76, 176 "line": 80,
164 "column": 13 177 "column": 13
165 }, 178 },
166 "end": { 179 "end": {
167 "line": 79, 180 "line": 83,
168 "column": 3 181 "column": 3
169 } 182 }
170 }, 183 },
@@ -173,11 +186,11 @@
173 "defaultMessage": "!!!User", 186 "defaultMessage": "!!!User",
174 "file": "src/containers/settings/EditServiceScreen.js", 187 "file": "src/containers/settings/EditServiceScreen.js",
175 "start": { 188 "start": {
176 "line": 80, 189 "line": 84,
177 "column": 13 190 "column": 13
178 }, 191 },
179 "end": { 192 "end": {
180 "line": 83, 193 "line": 87,
181 "column": 3 194 "column": 3
182 } 195 }
183 }, 196 },
@@ -186,11 +199,11 @@
186 "defaultMessage": "!!!Password", 199 "defaultMessage": "!!!Password",
187 "file": "src/containers/settings/EditServiceScreen.js", 200 "file": "src/containers/settings/EditServiceScreen.js",
188 "start": { 201 "start": {
189 "line": 84, 202 "line": 88,
190 "column": 17 203 "column": 17
191 }, 204 },
192 "end": { 205 "end": {
193 "line": 87, 206 "line": 91,
194 "column": 3 207 "column": 3
195 } 208 }
196 } 209 }
diff --git a/src/i18n/messages/src/containers/settings/EditSettingsScreen.json b/src/i18n/messages/src/containers/settings/EditSettingsScreen.json
index 5e084c1e1..42199503b 100644
--- a/src/i18n/messages/src/containers/settings/EditSettingsScreen.json
+++ b/src/i18n/messages/src/containers/settings/EditSettingsScreen.json
@@ -300,7 +300,7 @@
300 }, 300 },
301 { 301 {
302 "id": "settings.app.form.adaptableDarkMode", 302 "id": "settings.app.form.adaptableDarkMode",
303 "defaultMessage": "!!!Synchronize dark mode with my Mac's dark mode setting", 303 "defaultMessage": "!!!Synchronize dark mode with my OS's dark mode setting",
304 "file": "src/containers/settings/EditSettingsScreen.js", 304 "file": "src/containers/settings/EditSettingsScreen.js",
305 "start": { 305 "start": {
306 "line": 121, 306 "line": 121,
diff --git a/src/i18n/messages/src/features/recipeConnectionLost/components/WebControls.json b/src/i18n/messages/src/features/recipeConnectionLost/components/WebControls.json
new file mode 100644
index 000000000..f3bcaf345
--- /dev/null
+++ b/src/i18n/messages/src/features/recipeConnectionLost/components/WebControls.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "webControls.goHome",
4 "defaultMessage": "!!!Home",
5 "file": "src/features/recipeConnectionLost/components/WebControls.js",
6 "start": {
7 "line": 13,
8 "column": 10
9 },
10 "end": {
11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "webControls.openInBrowser",
17 "defaultMessage": "!!!Open in Browser",
18 "file": "src/features/recipeConnectionLost/components/WebControls.js",
19 "start": {
20 "line": 17,
21 "column": 17
22 },
23 "end": {
24 "line": 20,
25 "column": 3
26 }
27 },
28 {
29 "id": "webControls.back",
30 "defaultMessage": "!!!Back",
31 "file": "src/features/recipeConnectionLost/components/WebControls.js",
32 "start": {
33 "line": 21,
34 "column": 8
35 },
36 "end": {
37 "line": 24,
38 "column": 3
39 }
40 },
41 {
42 "id": "webControls.forward",
43 "defaultMessage": "!!!Forward",
44 "file": "src/features/recipeConnectionLost/components/WebControls.js",
45 "start": {
46 "line": 25,
47 "column": 11
48 },
49 "end": {
50 "line": 28,
51 "column": 3
52 }
53 },
54 {
55 "id": "webControls.reload",
56 "defaultMessage": "!!!Reload",
57 "file": "src/features/recipeConnectionLost/components/WebControls.js",
58 "start": {
59 "line": 29,
60 "column": 10
61 },
62 "end": {
63 "line": 32,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 594097288..e5f678759 100644
--- a/src/index.js
+++ b/src/index.js
@@ -37,6 +37,7 @@ import Tray from './lib/Tray';
37import Settings from './electron/Settings'; 37import Settings from './electron/Settings';
38import handleDeepLink from './electron/deepLinking'; 38import handleDeepLink from './electron/deepLinking';
39import { isPositionValid } from './electron/windowUtils'; 39import { isPositionValid } from './electron/windowUtils';
40// import askFormacOSPermissions from './electron/macOSPermissions';
40import { appId } from './package.json'; // eslint-disable-line import/no-unresolved 41import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
41import './electron/exception'; 42import './electron/exception';
42 43
@@ -46,10 +47,14 @@ import {
46} from './config'; 47} from './config';
47import { asarPath } from './helpers/asar-helpers'; 48import { asarPath } from './helpers/asar-helpers';
48import { isValidExternalURL } from './helpers/url-helpers'; 49import { isValidExternalURL } from './helpers/url-helpers';
49/* eslint-enable import/first */ 50import userAgent from './helpers/userAgent-helpers';
50 51
51const debug = require('debug')('Ferdi:App'); 52const debug = require('debug')('Ferdi:App');
52 53
54// Globally set useragent to fix user agent override in service workers
55debug('Set userAgent to ', userAgent());
56app.userAgentFallback = userAgent();
57
53// Keep a global reference of the window object, if you don't, the window will 58// Keep a global reference of the window object, if you don't, the window will
54// be closed automatically when the JavaScript object is garbage collected. 59// be closed automatically when the JavaScript object is garbage collected.
55let mainWindow; 60let mainWindow;
@@ -182,6 +187,7 @@ const createWindow = () => {
182 nodeIntegration: true, 187 nodeIntegration: true,
183 webviewTag: true, 188 webviewTag: true,
184 preload: path.join(__dirname, 'sentry.js'), 189 preload: path.join(__dirname, 'sentry.js'),
190 enableRemoteModule: true,
185 }, 191 },
186 }); 192 });
187 193
@@ -291,6 +297,11 @@ const createWindow = () => {
291 } 297 }
292 }); 298 });
293 299
300 // Asking for permissions like this currently crashes Ferdi
301 // if (isMac) {
302 // askFormacOSPermissions();
303 // }
304
294 mainWindow.on('show', () => { 305 mainWindow.on('show', () => {
295 debug('Skip taskbar: true'); 306 debug('Skip taskbar: true');
296 mainWindow.setSkipTaskbar(false); 307 mainWindow.setSkipTaskbar(false);
diff --git a/src/internal-server b/src/internal-server
Subproject 38fc9925d88971cee26cc08343da2f0e153c053 Subproject 95ae59926dbd88d55a5377be997558a9e112ab4
diff --git a/src/models/Recipe.js b/src/models/Recipe.js
index 6655f8310..dcb998a19 100644
--- a/src/models/Recipe.js
+++ b/src/models/Recipe.js
@@ -38,6 +38,8 @@ export default class Recipe {
38 38
39 disablewebsecurity = false; 39 disablewebsecurity = false;
40 40
41 autoHibernate = false;
42
41 constructor(data) { 43 constructor(data) {
42 if (!data) { 44 if (!data) {
43 throw Error('Recipe config not valid'); 45 throw Error('Recipe config not valid');
@@ -78,6 +80,8 @@ export default class Recipe {
78 80
79 this.disablewebsecurity = data.config.disablewebsecurity || this.disablewebsecurity; 81 this.disablewebsecurity = data.config.disablewebsecurity || this.disablewebsecurity;
80 82
83 this.autoHibernate = data.config.autoHibernate || this.autoHibernate;
84
81 this.message = data.config.message || this.message; 85 this.message = data.config.message || this.message;
82 } 86 }
83 87
diff --git a/src/models/Service.js b/src/models/Service.js
index f073ac9fc..dc8febe0b 100644
--- a/src/models/Service.js
+++ b/src/models/Service.js
@@ -2,6 +2,8 @@ import { autorun, computed, observable } from 'mobx';
2import normalizeUrl from 'normalize-url'; 2import normalizeUrl from 'normalize-url';
3import path from 'path'; 3import path from 'path';
4 4
5import userAgent from '../helpers/userAgent-helpers';
6
5const debug = require('debug')('Ferdi:Service'); 7const debug = require('debug')('Ferdi:Service');
6 8
7export const RESTRICTION_TYPES = { 9export const RESTRICTION_TYPES = {
@@ -74,6 +76,20 @@ export default class Service {
74 76
75 @observable restrictionType = null; 77 @observable restrictionType = null;
76 78
79 @observable isHibernationEnabled = false;
80
81 @observable lastUsed = Date.now(); // timestamp
82
83 @observable lastPoll = null;
84
85 @observable lastPollAnswer = null;
86
87 @observable lostRecipeConnection = false;
88
89 @observable lostRecipeReloadAttempt = 0;
90
91 @observable chromelessUserAgent = false;
92
77 constructor(data, recipe) { 93 constructor(data, recipe) {
78 if (!data) { 94 if (!data) {
79 console.error('Service config not valid'); 95 console.error('Service config not valid');
@@ -119,6 +135,8 @@ export default class Service {
119 135
120 this.spellcheckerLanguage = data.spellcheckerLanguage !== undefined ? data.spellcheckerLanguage : this.spellcheckerLanguage; 136 this.spellcheckerLanguage = data.spellcheckerLanguage !== undefined ? data.spellcheckerLanguage : this.spellcheckerLanguage;
121 137
138 this.isHibernationEnabled = data.isHibernationEnabled !== undefined ? data.isHibernationEnabled : this.isHibernationEnabled;
139
122 this.recipe = recipe; 140 this.recipe = recipe;
123 141
124 autorun(() => { 142 autorun(() => {
@@ -187,21 +205,34 @@ export default class Service {
187 } 205 }
188 206
189 @computed get userAgent() { 207 @computed get userAgent() {
190 let { userAgent } = window.navigator; 208 let ua = userAgent(this.chromelessUserAgent);
191 if (typeof this.recipe.overrideUserAgent === 'function') { 209 if (typeof this.recipe.overrideUserAgent === 'function') {
192 userAgent = this.recipe.overrideUserAgent(); 210 ua = this.recipe.overrideUserAgent();
193 } 211 }
194 212
195 // Remove Ferdi as it can cause incompatabilities with services. 213 return ua;
196 // This way, Ferdi will look like a normal Chrome instance
197 userAgent = userAgent.replace(/(Ferdi|Electron)([^\s]+\s)/g, '');
198
199 return userAgent;
200 } 214 }
201 215
202 initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) { 216 initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) {
203 const webContents = this.webview.getWebContents(); 217 const webContents = this.webview.getWebContents();
204 218
219 const handleUserAgent = (url, forwardingHack = false) => {
220 if (url.startsWith('https://accounts.google.com')) {
221 if (!this.chromelessUserAgent) {
222 debug('Setting user agent to chromeless for url', url);
223 this.webview.setUserAgent(userAgent(true));
224 if (forwardingHack) {
225 this.webview.loadURL(url);
226 }
227 this.chromelessUserAgent = true;
228 }
229 } else if (this.chromelessUserAgent) {
230 debug('Setting user agent to contain chrome');
231 this.webview.setUserAgent(this.userAgent);
232 this.chromelessUserAgent = false;
233 }
234 };
235
205 this.webview.addEventListener('ipc-message', e => handleIPCMessage({ 236 this.webview.addEventListener('ipc-message', e => handleIPCMessage({
206 serviceId: this.id, 237 serviceId: this.id,
207 channel: e.channel, 238 channel: e.channel,
@@ -209,7 +240,6 @@ export default class Service {
209 })); 240 }));
210 241
211 this.webview.addEventListener('new-window', (event, url, frameName, options) => { 242 this.webview.addEventListener('new-window', (event, url, frameName, options) => {
212 console.log('open window', event, url, frameName, options);
213 openWindow({ 243 openWindow({
214 event, 244 event,
215 url, 245 url,
@@ -218,6 +248,9 @@ export default class Service {
218 }); 248 });
219 }); 249 });
220 250
251
252 this.webview.addEventListener('will-navigate', event => handleUserAgent(event.url, true));
253
221 this.webview.addEventListener('did-start-loading', (event) => { 254 this.webview.addEventListener('did-start-loading', (event) => {
222 debug('Did start load', this.name, event); 255 debug('Did start load', this.name, event);
223 256
@@ -235,7 +268,10 @@ export default class Service {
235 }; 268 };
236 269
237 this.webview.addEventListener('did-frame-finish-load', didLoad.bind(this)); 270 this.webview.addEventListener('did-frame-finish-load', didLoad.bind(this));
238 this.webview.addEventListener('did-navigate', didLoad.bind(this)); 271 this.webview.addEventListener('did-navigate', (event) => {
272 handleUserAgent(event.url);
273 didLoad();
274 });
239 275
240 this.webview.addEventListener('did-fail-load', (event) => { 276 this.webview.addEventListener('did-fail-load', (event) => {
241 debug('Service failed to load', this.name, event); 277 debug('Service failed to load', this.name, event);
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index e2e3760a8..da6055e5f 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -218,13 +218,16 @@ export default class AppStore extends Store {
218 // macOS catalina notifications hack 218 // macOS catalina notifications hack
219 // notifications got stuck after upgrade but forcing a notification 219 // notifications got stuck after upgrade but forcing a notification
220 // via `new Notification` triggered the permission request 220 // via `new Notification` triggered the permission request
221 if (isMac && !localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) { 221 if (isMac) {
222 // eslint-disable-next-line no-new 222 if (!localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) {
223 new window.Notification('Welcome to Franz 5', { 223 debug('Triggering macOS Catalina notification permission trigger');
224 body: 'Have a wonderful day & happy messaging.', 224 // eslint-disable-next-line no-new
225 }); 225 new window.Notification('Welcome to Franz 5', {
226 body: 'Have a wonderful day & happy messaging.',
227 });
226 228
227 localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true); 229 localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true);
230 }
228 } 231 }
229 } 232 }
230 233
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 19e6f8299..80c7d7e81 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -5,7 +5,7 @@ import {
5 computed, 5 computed,
6 observable, 6 observable,
7} from 'mobx'; 7} from 'mobx';
8import { remove } from 'lodash'; 8import { debounce, remove } from 'lodash';
9import ms from 'ms'; 9import ms from 'ms';
10import fs from 'fs-extra'; 10import fs from 'fs-extra';
11import path from 'path'; 11import path from 'path';
@@ -127,6 +127,60 @@ export default class ServicesStore extends Store {
127 ); 127 );
128 } 128 }
129 129
130 initialize() {
131 super.initialize();
132
133 // Check services to become hibernated
134 this.serviceMaintenanceTick();
135 }
136
137 teardown() {
138 super.teardown();
139
140 // Stop checking services for hibernation
141 this.serviceMaintenanceTick.cancel();
142 }
143
144 /**
145 * Сheck for services to become hibernated.
146 */
147 serviceMaintenanceTick = debounce(() => {
148 this._serviceMaintenance();
149 this.serviceMaintenanceTick();
150 debug('Service maintenance tick');
151 }, ms('10s'));
152
153 /**
154 * Run various maintenance tasks on services
155 */
156 _serviceMaintenance() {
157 this.all.forEach((service) => {
158 // Defines which services should be hibernated.
159 if (!service.isActive && (Date.now() - service.lastUsed > ms('5m'))) {
160 // If service is stale for 5 min, hibernate it.
161 this._hibernate({ serviceId: service.id });
162 }
163
164 if (service.lastPoll && (service.lastPoll) - service.lastPollAnswer > ms('30s')) {
165 // If service did not reply for more than 30s try to reload.
166 if (!service.isActive) {
167 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) {
168 service.webview.reload();
169 service.lostRecipeReloadAttempt += 1;
170
171 service.lostRecipeConnection = false;
172 }
173 } else {
174 service.lostRecipeConnection = true;
175 }
176 } else {
177 service.lostRecipeConnection = false;
178 service.lostRecipeReloadAttempt = 0;
179 }
180 });
181 }
182
183 // Computed props
130 @computed get all() { 184 @computed get all() {
131 if (this.stores.user.isLoggedIn) { 185 if (this.stores.user.isLoggedIn) {
132 const services = this.allServicesRequest.execute().result; 186 const services = this.allServicesRequest.execute().result;
@@ -379,6 +433,7 @@ export default class ServicesStore extends Store {
379 this.all[index].isActive = false; 433 this.all[index].isActive = false;
380 }); 434 });
381 service.isActive = true; 435 service.isActive = true;
436 service.lastUsed = Date.now();
382 437
383 // Update list of last used services 438 // Update list of last used services
384 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId); 439 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId);
@@ -475,10 +530,16 @@ export default class ServicesStore extends Store {
475 const service = this.one(serviceId); 530 const service = this.one(serviceId);
476 531
477 if (channel === 'hello') { 532 if (channel === 'hello') {
533 debug('Received hello event from', serviceId);
534
478 this._initRecipePolling(service.id); 535 this._initRecipePolling(service.id);
479 this._initializeServiceRecipeInWebview(serviceId); 536 this._initializeServiceRecipeInWebview(serviceId);
480 this._shareSettingsWithServiceProcess(); 537 this._shareSettingsWithServiceProcess();
538 } else if (channel === 'alive') {
539 service.lastPollAnswer = Date.now();
481 } else if (channel === 'messages') { 540 } else if (channel === 'messages') {
541 debug(`Received unread message info from '${serviceId}'`, args[0]);
542
482 this.actions.service.setUnreadMessageCount({ 543 this.actions.service.setUnreadMessageCount({
483 serviceId, 544 serviceId,
484 count: { 545 count: {
@@ -600,6 +661,7 @@ export default class ServicesStore extends Store {
600 if (!service.isEnabled) return; 661 if (!service.isEnabled) return;
601 662
602 service.resetMessageCount(); 663 service.resetMessageCount();
664 service.lostRecipeConnection = false;
603 665
604 // service.webview.loadURL(service.url); 666 // service.webview.loadURL(service.url);
605 service.webview.reload(); 667 service.webview.reload();
@@ -777,7 +839,7 @@ export default class ServicesStore extends Store {
777 const isMuted = isAppMuted || service.isMuted; 839 const isMuted = isAppMuted || service.isMuted;
778 840
779 if (isAttached) { 841 if (isAttached) {
780 service.webview.setAudioMuted(isMuted); 842 service.webview.audioMuted = isMuted;
781 } 843 }
782 }); 844 });
783 } 845 }
@@ -863,6 +925,7 @@ export default class ServicesStore extends Store {
863 service.webview.send('poll'); 925 service.webview.send('poll');
864 926
865 service.timer = setTimeout(loop, delay); 927 service.timer = setTimeout(loop, delay);
928 service.lastPoll = Date.now();
866 }; 929 };
867 930
868 loop(); 931 loop();
diff --git a/src/webview/lib/RecipeWebview.js b/src/webview/lib/RecipeWebview.js
index 4fac21c55..add5fffa6 100644
--- a/src/webview/lib/RecipeWebview.js
+++ b/src/webview/lib/RecipeWebview.js
@@ -14,6 +14,10 @@ class RecipeWebview {
14 this.loopFunc(); 14 this.loopFunc();
15 15
16 debug('Poll event'); 16 debug('Poll event');
17
18 // This event is for checking if the service recipe is still actively
19 // communicating with the client
20 ipcRenderer.sendToHost('alive');
17 }); 21 });
18 } 22 }
19 23