aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/ServicesStore.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores/ServicesStore.js')
-rw-r--r--src/stores/ServicesStore.js131
1 files changed, 115 insertions, 16 deletions
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 448260638..21ed0a234 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -20,6 +20,8 @@ import { workspaceStore } from '../features/workspaces';
20import { serviceLimitStore } from '../features/serviceLimit'; 20import { serviceLimitStore } from '../features/serviceLimit';
21import { RESTRICTION_TYPES } from '../models/Service'; 21import { RESTRICTION_TYPES } from '../models/Service';
22import { KEEP_WS_LOADED_USID } from '../config'; 22import { KEEP_WS_LOADED_USID } from '../config';
23import { TODOS_RECIPE_ID } from '../features/todos';
24import { SPELLCHECKER_LOCALES } from '../i18n/languages';
23 25
24const debug = require('debug')('Ferdi:ServiceStore'); 26const debug = require('debug')('Ferdi:ServiceStore');
25 27
@@ -82,7 +84,9 @@ export default class ServicesStore extends Store {
82 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this)); 84 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this));
83 this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); 85 this.actions.service.openDevTools.listen(this._openDevTools.bind(this));
84 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); 86 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this));
85 this.actions.service.setHibernation.listen(this._setHibernation.bind(this)); 87 this.actions.service.hibernate.listen(this._hibernate.bind(this));
88 this.actions.service.awake.listen(this._awake.bind(this));
89 this.actions.service.resetLastPollTimer.listen(this._resetLastPollTimer.bind(this));
86 this.actions.service.shareSettingsWithServiceProcess.listen(this._shareSettingsWithServiceProcess.bind(this)); 90 this.actions.service.shareSettingsWithServiceProcess.listen(this._shareSettingsWithServiceProcess.bind(this));
87 91
88 this.registerReactions([ 92 this.registerReactions([
@@ -93,6 +97,7 @@ export default class ServicesStore extends Store {
93 this._logoutReaction.bind(this), 97 this._logoutReaction.bind(this),
94 this._handleMuteSettings.bind(this), 98 this._handleMuteSettings.bind(this),
95 this._restrictServiceAccess.bind(this), 99 this._restrictServiceAccess.bind(this),
100 this._checkForActiveService.bind(this),
96 ]); 101 ]);
97 102
98 // Just bind this 103 // Just bind this
@@ -155,16 +160,24 @@ export default class ServicesStore extends Store {
155 */ 160 */
156 _serviceMaintenance() { 161 _serviceMaintenance() {
157 this.all.forEach((service) => { 162 this.all.forEach((service) => {
158 if (service.lastPoll && (service.lastPoll) - service.lastPollAnswer > ms('30s')) { 163 // Defines which services should be hibernated.
159 // If service did not reply for more than 30s try to reload. 164 if (!service.isActive && (Date.now() - service.lastUsed > ms('5m'))) {
165 // If service is stale for 5 min, hibernate it.
166 this._hibernate({ serviceId: service.id });
167 }
168
169 if (service.lastPoll && (service.lastPoll - service.lastPollAnswer > ms('1m'))) {
170 // If service did not reply for more than 1m try to reload.
160 if (!service.isActive) { 171 if (!service.isActive) {
161 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) { 172 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) {
162 service.webview.reload(); 173 debug(`Reloading service: ${service.name} (${service.id}). Attempt: ${service.lostRecipeReloadAttempt}`);
174 // service.webview.reload();
163 service.lostRecipeReloadAttempt += 1; 175 service.lostRecipeReloadAttempt += 1;
164 176
165 service.lostRecipeConnection = false; 177 service.lostRecipeConnection = false;
166 } 178 }
167 } else { 179 } else {
180 debug(`Service lost connection: ${service.name} (${service.id}).`);
168 service.lostRecipeConnection = true; 181 service.lostRecipeConnection = true;
169 } 182 }
170 } else { 183 } else {
@@ -256,6 +269,14 @@ export default class ServicesStore extends Store {
256 return null; 269 return null;
257 } 270 }
258 271
272 @computed get isTodosServiceAdded() {
273 return this.allDisplayed.find(service => service.recipe.id === TODOS_RECIPE_ID && service.isEnabled) || null;
274 }
275
276 @computed get isTodosServiceActive() {
277 return this.active && this.active.recipe.id === TODOS_RECIPE_ID;
278 }
279
259 one(id) { 280 one(id) {
260 return this.all.find(service => service.id === id); 281 return this.all.find(service => service.id === id);
261 } 282 }
@@ -265,10 +286,34 @@ export default class ServicesStore extends Store {
265 } 286 }
266 287
267 // Actions 288 // Actions
268 @action async _createService({ recipeId, serviceData, redirect = true }) { 289 async _createService({
290 recipeId, serviceData, redirect = true, skipCleanup = false,
291 }) {
269 if (serviceLimitStore.userHasReachedServiceLimit) return; 292 if (serviceLimitStore.userHasReachedServiceLimit) return;
270 293
271 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 294 if (!this.stores.recipes.isInstalled(recipeId)) {
295 debug(`Recipe "${recipeId}" is not installed, installing recipe`);
296 await this.stores.recipes._install({ recipeId });
297 debug(`Recipe "${recipeId}" installed`);
298 }
299
300 // set default values for serviceData
301 Object.assign({
302 isEnabled: true,
303 isHibernationEnabled: false,
304 isNotificationEnabled: true,
305 isBadgeEnabled: true,
306 isMuted: false,
307 customIcon: false,
308 isDarkModeEnabled: false,
309 spellcheckerLanguage: SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage],
310 }, serviceData);
311
312 let data = serviceData;
313
314 if (!skipCleanup) {
315 data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
316 }
272 317
273 const response = await this.createServiceRequest.execute(recipeId, data)._promise; 318 const response = await this.createServiceRequest.execute(recipeId, data)._promise;
274 319
@@ -427,8 +472,13 @@ export default class ServicesStore extends Store {
427 this.all[index].isActive = false; 472 this.all[index].isActive = false;
428 }); 473 });
429 service.isActive = true; 474 service.isActive = true;
475 this._awake({ serviceId: service.id });
430 service.lastUsed = Date.now(); 476 service.lastUsed = Date.now();
431 477
478 if (this.active.recipe.id === TODOS_RECIPE_ID && !this.stores.todos.settings.isFeatureEnabledByUser) {
479 this.actions.todos.toggleTodosFeatureVisibility();
480 }
481
432 // Update list of last used services 482 // Update list of last used services
433 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId); 483 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId);
434 this.lastUsedServices.unshift(serviceId); 484 this.lastUsedServices.unshift(serviceId);
@@ -617,10 +667,10 @@ export default class ServicesStore extends Store {
617 @action _sendIPCMessage({ serviceId, channel, args }) { 667 @action _sendIPCMessage({ serviceId, channel, args }) {
618 const service = this.one(serviceId); 668 const service = this.one(serviceId);
619 669
620 if (service.webview) { 670 // Make sure the args are clean, otherwise ElectronJS can't transmit them
621 // Make sure the args are clean, otherwise ElectronJS can't transmit them 671 const cleanArgs = JSON.parse(JSON.stringify(args));
622 const cleanArgs = JSON.parse(JSON.stringify(args));
623 672
673 if (service.webview) {
624 service.webview.send(channel, cleanArgs); 674 service.webview.send(channel, cleanArgs);
625 } 675 }
626 } 676 }
@@ -659,8 +709,11 @@ export default class ServicesStore extends Store {
659 service.resetMessageCount(); 709 service.resetMessageCount();
660 service.lostRecipeConnection = false; 710 service.lostRecipeConnection = false;
661 711
662 // service.webview.loadURL(service.url); 712 if (service.recipe.id === TODOS_RECIPE_ID) {
663 service.webview.reload(); 713 return this.actions.todos.reload();
714 }
715
716 return service.webview.loadURL(service.url);
664 } 717 }
665 718
666 @action _reloadActive() { 719 @action _reloadActive() {
@@ -743,23 +796,57 @@ export default class ServicesStore extends Store {
743 796
744 @action _openDevTools({ serviceId }) { 797 @action _openDevTools({ serviceId }) {
745 const service = this.one(serviceId); 798 const service = this.one(serviceId);
746 799 if (service.recipe.id === TODOS_RECIPE_ID) {
747 service.webview.openDevTools(); 800 this.actions.todos.openDevTools();
801 } else {
802 service.webview.openDevTools();
803 }
748 } 804 }
749 805
750 @action _openDevToolsForActiveService() { 806 @action _openDevToolsForActiveService() {
751 const service = this.active; 807 const service = this.active;
752 808
753 if (service) { 809 if (service) {
754 service.webview.openDevTools(); 810 this._openDevTools({ serviceId: service.id });
755 } else { 811 } else {
756 debug('No service is active'); 812 debug('No service is active');
757 } 813 }
758 } 814 }
759 815
760 @action _setHibernation({ serviceId, hibernating }) { 816 @action _hibernate({ serviceId }) {
817 const service = this.one(serviceId);
818 if (service.isActive || !service.isHibernationEnabled) {
819 debug('Skipping service hibernation');
820 return;
821 }
822
823 debug(`Hibernate ${service.name}`);
824
825 service.isHibernating = true;
826 }
827
828 @action _awake({ serviceId }) {
761 const service = this.one(serviceId); 829 const service = this.one(serviceId);
762 service.isHibernating = hibernating; 830 service.isHibernating = false;
831 service.liveFrom = Date.now();
832 }
833
834 @action _resetLastPollTimer({ serviceId = null }) {
835 debug(`Reset last poll timer for ${serviceId ? `service: "${serviceId}"` : 'all services'}`);
836
837 const resetTimer = (service) => {
838 service.lastPollAnswer = Date.now();
839 service.lastPoll = Date.now();
840 };
841
842 if (!serviceId) {
843 this.allDisplayed.forEach(service => resetTimer(service));
844 } else {
845 const service = this.one(serviceId);
846 if (service) {
847 resetTimer(service);
848 }
849 }
763 } 850 }
764 851
765 // Reactions 852 // Reactions
@@ -893,6 +980,18 @@ export default class ServicesStore extends Store {
893 }); 980 });
894 } 981 }
895 982
983 _checkForActiveService() {
984 if (!this.stores.router.location || this.stores.router.location.pathname.includes('auth/signup')) {
985 return;
986 }
987
988 if (this.allDisplayed.findIndex(service => service.isActive) === -1 && this.allDisplayed.length !== 0) {
989 debug('No active service found, setting active service to index 0');
990
991 this._setActive({ serviceId: this.allDisplayed[0].id });
992 }
993 }
994
896 // Helper 995 // Helper
897 _initializeServiceRecipeInWebview(serviceId) { 996 _initializeServiceRecipeInWebview(serviceId) {
898 const service = this.one(serviceId); 997 const service = this.one(serviceId);