aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Bennett <hello@vantezzen.io>2020-04-17 14:32:53 +0200
committerLibravatar GitHub <noreply@github.com>2020-04-17 13:32:53 +0100
commit3239172af9fb68226e1e11392b16f833b726ad72 (patch)
treefdafdea17c8873cffdec64351e532ffdb2c7d233
parentNew Crowdin translations (#570) (diff)
downloadferdium-app-3239172af9fb68226e1e11392b16f833b726ad72.tar.gz
ferdium-app-3239172af9fb68226e1e11392b16f833b726ad72.tar.zst
ferdium-app-3239172af9fb68226e1e11392b16f833b726ad72.zip
Merge Franz 5.5.0 beta.1 and 2 (#549)
* Automatic i18n update (i18n.meetfranz.com) * Fix zoom not working * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Add environment variable FRANZ_APPDATA_DIR * Add environment variable for config FRANZ_APPDATA_DIR * fix stuff that shouldn't need fixing in the first place * notarize app * bump version to 5.4.1 * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * update to electron 7.1.7 * bump version to 5.5.0 * Automatic i18n update (i18n.meetfranz.com) * Bump electron to 7.1.10 * plan changes * filter plan variants * add missing string * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Replace/remove deprecated electron function calls Electron replaced several function calls with object properties. Franz still uses some of these deprecated functions, so this commit will replace these with their new property counterpart. This commit is only transferring the changes made in getferdi/ferdi#371 and getferdi/ferdi#384 into the upstream repository. * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Update to electron 8.0.1 * feat(Service): Add service hibernation to save system resources * Fix linting issues * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Bump version to electron 8.1.1 * update strings * update electron-notarize * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Automatic i18n update (i18n.meetfranz.com) * Add audio & video permission request for cataline * Automatic i18n update (i18n.meetfranz.com) * bump version to beta 1 * Automatic i18n update (i18n.meetfranz.com) * enable external links * Fix main content href * Automatic i18n update (i18n.meetfranz.com) * fix(Service): Fix issue with user agent override in service workers * Automatic i18n update (i18n.meetfranz.com) * fix macOS string replacement * Automatic i18n update (i18n.meetfranz.com) * fix(Experimental): Fix Google signin * fix(Service): Add integrity checks to prevent services recipes getting lost * Remove service reload on app-resume * update strings * bump version to beta 2 * Automatic i18n update (i18n.meetfranz.com) * bump electron-builder and electron-updater deps * test video permissions * fix `extendInfo` keys * fix typo * fix permission request * update camera/microphone entitlements * Automatic i18n update (i18n.meetfranz.com) * Test screen capture permissions * unpack `mac-screen-capture-permissions` * don't require localstorage temp key for permissions request * move macOS permission request to main process * Fix checking for permissions * fix(macOS): Ask the user to move Franz to the `/Applications` Folder * only try to recover recipe when user is online * update service request link * Update changelog * Reset `service.lostRecipeReloadAttempt` once service is alive again * Update translations * Fix crash on macOS * Fix merge errors * Fix lint * Disable ConnectionLostBanner * #551 Add information about Ferdi to the user agent * Fix requested changes * Remove tsbuildinfo files * Add .tsbuildinfo files to gitignore * Fix "Cannot destructure property 'app' of '_electron.remote' as it is undefined" Co-authored-by: FranzBot <i18n@meetfranz.com> Co-authored-by: Makazzz <makazzzpro@live.ca> Co-authored-by: Stefan Malzner <stefan@adlk.io>
-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