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.js452
1 files changed, 295 insertions, 157 deletions
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 9b69cb7c6..4ccb995ae 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -1,25 +1,21 @@
1import { shell } from 'electron'; 1import { shell } from 'electron';
2import { 2import { action, reaction, computed, observable } from 'mobx';
3 action,
4 reaction,
5 computed,
6 observable,
7} from 'mobx';
8import { debounce, remove } from 'lodash'; 3import { debounce, remove } from 'lodash';
9import ms from 'ms'; 4import ms from 'ms';
10import { app } from '@electron/remote'; 5import { app } from '@electron/remote';
11import fs from 'fs-extra'; 6import { ensureFileSync, pathExistsSync, writeFileSync } from 'fs-extra';
12import path from 'path'; 7import { join } from 'path';
13 8
14import Store from './lib/Store'; 9import Store from './lib/Store';
15import Request from './lib/Request'; 10import Request from './lib/Request';
16import CachedRequest from './lib/CachedRequest'; 11import CachedRequest from './lib/CachedRequest';
17import { matchRoute } from '../helpers/routing-helpers'; 12import { matchRoute } from '../helpers/routing-helpers';
18import { isInTimeframe } from '../helpers/schedule-helpers'; 13import { isInTimeframe } from '../helpers/schedule-helpers';
19import { getRecipeDirectory, getDevRecipeDirectory } from '../helpers/recipe-helpers'; 14import {
15 getRecipeDirectory,
16 getDevRecipeDirectory,
17} from '../helpers/recipe-helpers';
20import { workspaceStore } from '../features/workspaces'; 18import { workspaceStore } from '../features/workspaces';
21import { serviceLimitStore } from '../features/serviceLimit';
22import { RESTRICTION_TYPES } from '../models/Service';
23import { KEEP_WS_LOADED_USID } from '../config'; 19import { KEEP_WS_LOADED_USID } from '../config';
24import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 20import { SPELLCHECKER_LOCALES } from '../i18n/languages';
25 21
@@ -32,7 +28,10 @@ export default class ServicesStore extends Store {
32 28
33 @observable updateServiceRequest = new Request(this.api.services, 'update'); 29 @observable updateServiceRequest = new Request(this.api.services, 'update');
34 30
35 @observable reorderServicesRequest = new Request(this.api.services, 'reorder'); 31 @observable reorderServicesRequest = new Request(
32 this.api.services,
33 'reorder',
34 );
36 35
37 @observable deleteServiceRequest = new Request(this.api.services, 'delete'); 36 @observable deleteServiceRequest = new Request(this.api.services, 'delete');
38 37
@@ -53,22 +52,36 @@ export default class ServicesStore extends Store {
53 this.actions.service.blurActive.listen(this._blurActive.bind(this)); 52 this.actions.service.blurActive.listen(this._blurActive.bind(this));
54 this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this)); 53 this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this));
55 this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this)); 54 this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this));
56 this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); 55 this.actions.service.showAddServiceInterface.listen(
56 this._showAddServiceInterface.bind(this),
57 );
57 this.actions.service.createService.listen(this._createService.bind(this)); 58 this.actions.service.createService.listen(this._createService.bind(this));
58 this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this)); 59 this.actions.service.createFromLegacyService.listen(
60 this._createFromLegacyService.bind(this),
61 );
59 this.actions.service.updateService.listen(this._updateService.bind(this)); 62 this.actions.service.updateService.listen(this._updateService.bind(this));
60 this.actions.service.deleteService.listen(this._deleteService.bind(this)); 63 this.actions.service.deleteService.listen(this._deleteService.bind(this));
61 this.actions.service.openRecipeFile.listen(this._openRecipeFile.bind(this)); 64 this.actions.service.openRecipeFile.listen(this._openRecipeFile.bind(this));
62 this.actions.service.clearCache.listen(this._clearCache.bind(this)); 65 this.actions.service.clearCache.listen(this._clearCache.bind(this));
63 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); 66 this.actions.service.setWebviewReference.listen(
67 this._setWebviewReference.bind(this),
68 );
64 this.actions.service.detachService.listen(this._detachService.bind(this)); 69 this.actions.service.detachService.listen(this._detachService.bind(this));
65 this.actions.service.focusService.listen(this._focusService.bind(this)); 70 this.actions.service.focusService.listen(this._focusService.bind(this));
66 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); 71 this.actions.service.focusActiveService.listen(
72 this._focusActiveService.bind(this),
73 );
67 this.actions.service.toggleService.listen(this._toggleService.bind(this)); 74 this.actions.service.toggleService.listen(this._toggleService.bind(this));
68 this.actions.service.handleIPCMessage.listen(this._handleIPCMessage.bind(this)); 75 this.actions.service.handleIPCMessage.listen(
76 this._handleIPCMessage.bind(this),
77 );
69 this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this)); 78 this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this));
70 this.actions.service.sendIPCMessageToAllServices.listen(this._sendIPCMessageToAllServices.bind(this)); 79 this.actions.service.sendIPCMessageToAllServices.listen(
71 this.actions.service.setUnreadMessageCount.listen(this._setUnreadMessageCount.bind(this)); 80 this._sendIPCMessageToAllServices.bind(this),
81 );
82 this.actions.service.setUnreadMessageCount.listen(
83 this._setUnreadMessageCount.bind(this),
84 );
72 this.actions.service.openWindow.listen(this._openWindow.bind(this)); 85 this.actions.service.openWindow.listen(this._openWindow.bind(this));
73 this.actions.service.filter.listen(this._filter.bind(this)); 86 this.actions.service.filter.listen(this._filter.bind(this));
74 this.actions.service.resetFilter.listen(this._resetFilter.bind(this)); 87 this.actions.service.resetFilter.listen(this._resetFilter.bind(this));
@@ -76,16 +89,27 @@ export default class ServicesStore extends Store {
76 this.actions.service.reload.listen(this._reload.bind(this)); 89 this.actions.service.reload.listen(this._reload.bind(this));
77 this.actions.service.reloadActive.listen(this._reloadActive.bind(this)); 90 this.actions.service.reloadActive.listen(this._reloadActive.bind(this));
78 this.actions.service.reloadAll.listen(this._reloadAll.bind(this)); 91 this.actions.service.reloadAll.listen(this._reloadAll.bind(this));
79 this.actions.service.reloadUpdatedServices.listen(this._reloadUpdatedServices.bind(this)); 92 this.actions.service.reloadUpdatedServices.listen(
93 this._reloadUpdatedServices.bind(this),
94 );
80 this.actions.service.reorder.listen(this._reorder.bind(this)); 95 this.actions.service.reorder.listen(this._reorder.bind(this));
81 this.actions.service.toggleNotifications.listen(this._toggleNotifications.bind(this)); 96 this.actions.service.toggleNotifications.listen(
97 this._toggleNotifications.bind(this),
98 );
82 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this)); 99 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this));
100 this.actions.service.toggleDarkMode.listen(this._toggleDarkMode.bind(this));
83 this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); 101 this.actions.service.openDevTools.listen(this._openDevTools.bind(this));
84 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); 102 this.actions.service.openDevToolsForActiveService.listen(
103 this._openDevToolsForActiveService.bind(this),
104 );
85 this.actions.service.hibernate.listen(this._hibernate.bind(this)); 105 this.actions.service.hibernate.listen(this._hibernate.bind(this));
86 this.actions.service.awake.listen(this._awake.bind(this)); 106 this.actions.service.awake.listen(this._awake.bind(this));
87 this.actions.service.resetLastPollTimer.listen(this._resetLastPollTimer.bind(this)); 107 this.actions.service.resetLastPollTimer.listen(
88 this.actions.service.shareSettingsWithServiceProcess.listen(this._shareSettingsWithServiceProcess.bind(this)); 108 this._resetLastPollTimer.bind(this),
109 );
110 this.actions.service.shareSettingsWithServiceProcess.listen(
111 this._shareSettingsWithServiceProcess.bind(this),
112 );
89 113
90 this.registerReactions([ 114 this.registerReactions([
91 this._focusServiceReaction.bind(this), 115 this._focusServiceReaction.bind(this),
@@ -94,7 +118,6 @@ export default class ServicesStore extends Store {
94 this._saveActiveService.bind(this), 118 this._saveActiveService.bind(this),
95 this._logoutReaction.bind(this), 119 this._logoutReaction.bind(this),
96 this._handleMuteSettings.bind(this), 120 this._handleMuteSettings.bind(this),
97 this._restrictServiceAccess.bind(this),
98 this._checkForActiveService.bind(this), 121 this._checkForActiveService.bind(this),
99 ]); 122 ]);
100 123
@@ -167,18 +190,42 @@ export default class ServicesStore extends Store {
167 * Run various maintenance tasks on services 190 * Run various maintenance tasks on services
168 */ 191 */
169 _serviceMaintenance() { 192 _serviceMaintenance() {
170 this.all.forEach((service) => { 193 this.all.forEach(service => {
171 // Defines which services should be hibernated. 194 // Defines which services should be hibernated or woken up
172 if (!service.isActive && (Date.now() - service.lastUsed > ms('5m'))) { 195 if (!service.isActive) {
173 // If service is stale for 5 min, hibernate it. 196 if (
174 this._hibernate({ serviceId: service.id }); 197 !service.lastHibernated &&
198 Date.now() - service.lastUsed >
199 ms(`${this.stores.settings.all.app.hibernationStrategy}s`)
200 ) {
201 // If service is stale, hibernate it.
202 this._hibernate({ serviceId: service.id });
203 }
204
205 if (
206 service.lastHibernated &&
207 Number(this.stores.settings.all.app.wakeUpStrategy) > 0
208 ) {
209 // If service is in hibernation and the wakeup time has elapsed, wake it.
210 if (
211 Date.now() - service.lastHibernated >
212 ms(`${this.stores.settings.all.app.wakeUpStrategy}s`)
213 ) {
214 this._awake({ serviceId: service.id });
215 }
216 }
175 } 217 }
176 218
177 if (service.lastPoll && (service.lastPoll - service.lastPollAnswer > ms('1m'))) { 219 if (
220 service.lastPoll &&
221 service.lastPoll - service.lastPollAnswer > ms('1m')
222 ) {
178 // If service did not reply for more than 1m try to reload. 223 // If service did not reply for more than 1m try to reload.
179 if (!service.isActive) { 224 if (!service.isActive) {
180 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) { 225 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) {
181 debug(`Reloading service: ${service.name} (${service.id}). Attempt: ${service.lostRecipeReloadAttempt}`); 226 debug(
227 `Reloading service: ${service.name} (${service.id}). Attempt: ${service.lostRecipeReloadAttempt}`,
228 );
182 // service.webview.reload(); 229 // service.webview.reload();
183 service.lostRecipeReloadAttempt += 1; 230 service.lostRecipeReloadAttempt += 1;
184 231
@@ -200,10 +247,16 @@ export default class ServicesStore extends Store {
200 if (this.stores.user.isLoggedIn) { 247 if (this.stores.user.isLoggedIn) {
201 const services = this.allServicesRequest.execute().result; 248 const services = this.allServicesRequest.execute().result;
202 if (services) { 249 if (services) {
203 return observable(services.slice().slice().sort((a, b) => a.order - b.order).map((s, index) => { 250 return observable(
204 s.index = index; 251 services
205 return s; 252 .slice()
206 })); 253 .slice()
254 .sort((a, b) => a.order - b.order)
255 .map((s, index) => {
256 s.index = index;
257 return s;
258 }),
259 );
207 } 260 }
208 } 261 }
209 return []; 262 return [];
@@ -214,7 +267,9 @@ export default class ServicesStore extends Store {
214 } 267 }
215 268
216 @computed get allDisplayed() { 269 @computed get allDisplayed() {
217 const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; 270 const services = this.stores.settings.all.app.showDisabledServices
271 ? this.all
272 : this.enabled;
218 return workspaceStore.filterServicesByActiveWorkspace(services); 273 return workspaceStore.filterServicesByActiveWorkspace(services);
219 } 274 }
220 275
@@ -223,7 +278,9 @@ export default class ServicesStore extends Store {
223 const { showDisabledServices } = this.stores.settings.all.app; 278 const { showDisabledServices } = this.stores.settings.all.app;
224 const { keepAllWorkspacesLoaded } = this.stores.workspaces.settings; 279 const { keepAllWorkspacesLoaded } = this.stores.workspaces.settings;
225 const services = this.allServicesRequest.execute().result || []; 280 const services = this.allServicesRequest.execute().result || [];
226 const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); 281 const filteredServices = showDisabledServices
282 ? services
283 : services.filter(service => service.isEnabled);
227 284
228 let displayedServices; 285 let displayedServices;
229 if (keepAllWorkspacesLoaded) { 286 if (keepAllWorkspacesLoaded) {
@@ -231,32 +288,38 @@ export default class ServicesStore extends Store {
231 displayedServices = filteredServices; 288 displayedServices = filteredServices;
232 } else { 289 } else {
233 // Keep all services in current workspace loaded 290 // Keep all services in current workspace loaded
234 displayedServices = workspaceStore.filterServicesByActiveWorkspace(filteredServices); 291 displayedServices =
292 workspaceStore.filterServicesByActiveWorkspace(filteredServices);
235 293
236 // Keep all services active in workspaces that should be kept loaded 294 // Keep all services active in workspaces that should be kept loaded
237 for (const workspace of this.stores.workspaces.workspaces) { 295 for (const workspace of this.stores.workspaces.workspaces) {
238 // Check if workspace needs to be kept loaded 296 // Check if workspace needs to be kept loaded
239 if (workspace.services.includes(KEEP_WS_LOADED_USID)) { 297 if (workspace.services.includes(KEEP_WS_LOADED_USID)) {
240 // Get services for workspace 298 // Get services for workspace
241 const serviceIDs = workspace.services.filter(i => i !== KEEP_WS_LOADED_USID); 299 const serviceIDs = workspace.services.filter(
242 const wsServices = filteredServices.filter(service => serviceIDs.includes(service.id)); 300 i => i !== KEEP_WS_LOADED_USID,
243 301 );
244 displayedServices = [ 302 const wsServices = filteredServices.filter(service =>
245 ...displayedServices, 303 serviceIDs.includes(service.id),
246 ...wsServices, 304 );
247 ]; 305
306 displayedServices = [...displayedServices, ...wsServices];
248 } 307 }
249 } 308 }
250 309
251 // Make sure every service is in the list only once 310 // Make sure every service is in the list only once
252 displayedServices = displayedServices.filter((v, i, a) => a.indexOf(v) === i); 311 displayedServices = displayedServices.filter(
312 (v, i, a) => a.indexOf(v) === i,
313 );
253 } 314 }
254 315
255 return displayedServices; 316 return displayedServices;
256 } 317 }
257 318
258 @computed get filtered() { 319 @computed get filtered() {
259 return this.all.filter(service => service.name.toLowerCase().includes(this.filterNeedle.toLowerCase())); 320 return this.all.filter(service =>
321 service.name.toLowerCase().includes(this.filterNeedle.toLowerCase()),
322 );
260 } 323 }
261 324
262 @computed get active() { 325 @computed get active() {
@@ -264,7 +327,10 @@ export default class ServicesStore extends Store {
264 } 327 }
265 328
266 @computed get activeSettings() { 329 @computed get activeSettings() {
267 const match = matchRoute('/settings/services/edit/:id', this.stores.router.location.pathname); 330 const match = matchRoute(
331 '/settings/services/edit/:id',
332 this.stores.router.location.pathname,
333 );
268 if (match) { 334 if (match) {
269 const activeService = this.one(match.id); 335 const activeService = this.one(match.id);
270 if (activeService) { 336 if (activeService) {
@@ -278,7 +344,11 @@ export default class ServicesStore extends Store {
278 } 344 }
279 345
280 @computed get isTodosServiceAdded() { 346 @computed get isTodosServiceAdded() {
281 return this.allDisplayed.find(service => service.isTodosService && service.isEnabled) || false; 347 return (
348 this.allDisplayed.find(
349 service => service.isTodosService && service.isEnabled,
350 ) || false
351 );
282 } 352 }
283 353
284 @computed get isTodosServiceActive() { 354 @computed get isTodosServiceActive() {
@@ -295,10 +365,11 @@ export default class ServicesStore extends Store {
295 365
296 // Actions 366 // Actions
297 async _createService({ 367 async _createService({
298 recipeId, serviceData, redirect = true, skipCleanup = false, 368 recipeId,
369 serviceData,
370 redirect = true,
371 skipCleanup = false,
299 }) { 372 }) {
300 if (serviceLimitStore.userHasReachedServiceLimit) return;
301
302 if (!this.stores.recipes.isInstalled(recipeId)) { 373 if (!this.stores.recipes.isInstalled(recipeId)) {
303 debug(`Recipe "${recipeId}" is not installed, installing recipe`); 374 debug(`Recipe "${recipeId}" is not installed, installing recipe`);
304 await this.stores.recipes._install({ recipeId }); 375 await this.stores.recipes._install({ recipeId });
@@ -307,17 +378,21 @@ export default class ServicesStore extends Store {
307 378
308 // set default values for serviceData 379 // set default values for serviceData
309 // eslint-disable-next-line prefer-object-spread 380 // eslint-disable-next-line prefer-object-spread
310 Object.assign({ 381 Object.assign(
311 isEnabled: true, 382 {
312 isHibernationEnabled: false, 383 isEnabled: true,
313 isNotificationEnabled: true, 384 isHibernationEnabled: false,
314 isBadgeEnabled: true, 385 isNotificationEnabled: true,
315 isMuted: false, 386 isBadgeEnabled: true,
316 customIcon: false, 387 isMuted: false,
317 isDarkModeEnabled: false, 388 customIcon: false,
318 spellcheckerLanguage: SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage], 389 isDarkModeEnabled: false,
319 userAgentPref: '', 390 spellcheckerLanguage:
320 }, serviceData); 391 SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage],
392 userAgentPref: '',
393 },
394 serviceData,
395 );
321 396
322 let data = serviceData; 397 let data = serviceData;
323 398
@@ -325,9 +400,10 @@ export default class ServicesStore extends Store {
325 data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 400 data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
326 } 401 }
327 402
328 const response = await this.createServiceRequest.execute(recipeId, data)._promise; 403 const response = await this.createServiceRequest.execute(recipeId, data)
404 ._promise;
329 405
330 this.allServicesRequest.patch((result) => { 406 this.allServicesRequest.patch(result => {
331 if (!result) return; 407 if (!result) return;
332 result.push(response.data); 408 result.push(response.data);
333 }); 409 });
@@ -371,7 +447,10 @@ export default class ServicesStore extends Store {
371 447
372 @action async _updateService({ serviceId, serviceData, redirect = true }) { 448 @action async _updateService({ serviceId, serviceData, redirect = true }) {
373 const service = this.one(serviceId); 449 const service = this.one(serviceId);
374 const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); 450 const data = this._cleanUpTeamIdAndCustomUrl(
451 service.recipe.id,
452 serviceData,
453 );
375 const request = this.updateServiceRequest.execute(serviceId, data); 454 const request = this.updateServiceRequest.execute(serviceId, data);
376 455
377 const newData = serviceData; 456 const newData = serviceData;
@@ -382,7 +461,7 @@ export default class ServicesStore extends Store {
382 newData.hasCustomUploadedIcon = true; 461 newData.hasCustomUploadedIcon = true;
383 } 462 }
384 463
385 this.allServicesRequest.patch((result) => { 464 this.allServicesRequest.patch(result => {
386 if (!result) return; 465 if (!result) return;
387 466
388 // patch custom icon deletion 467 // patch custom icon deletion
@@ -396,7 +475,10 @@ export default class ServicesStore extends Store {
396 newData.iconUrl = data.customIconUrl; 475 newData.iconUrl = data.customIconUrl;
397 } 476 }
398 477
399 Object.assign(result.find(c => c.id === serviceId), newData); 478 Object.assign(
479 result.find(c => c.id === serviceId),
480 newData,
481 );
400 }); 482 });
401 483
402 await request._promise; 484 await request._promise;
@@ -429,7 +511,7 @@ export default class ServicesStore extends Store {
429 this.stores.router.push(redirect); 511 this.stores.router.push(redirect);
430 } 512 }
431 513
432 this.allServicesRequest.patch((result) => { 514 this.allServicesRequest.patch(result => {
433 remove(result, c => c.id === serviceId); 515 remove(result, c => c.id === serviceId);
434 }); 516 });
435 517
@@ -443,9 +525,9 @@ export default class ServicesStore extends Store {
443 const devDirectory = getDevRecipeDirectory(recipe); 525 const devDirectory = getDevRecipeDirectory(recipe);
444 let directory; 526 let directory;
445 527
446 if (await fs.pathExists(normalDirectory)) { 528 if (pathExistsSync(normalDirectory)) {
447 directory = normalDirectory; 529 directory = normalDirectory;
448 } else if (await fs.pathExists(devDirectory)) { 530 } else if (pathExistsSync(devDirectory)) {
449 directory = devDirectory; 531 directory = devDirectory;
450 } else { 532 } else {
451 // Recipe cannot be found on drive 533 // Recipe cannot be found on drive
@@ -453,17 +535,19 @@ export default class ServicesStore extends Store {
453 } 535 }
454 536
455 // Create and open file 537 // Create and open file
456 const filePath = path.join(directory, file); 538 const filePath = join(directory, file);
457 if (file === 'user.js') { 539 if (file === 'user.js') {
458 if (!await fs.exists(filePath)) { 540 if (!pathExistsSync(filePath)) {
459 await fs.writeFile(filePath, `module.exports = (config, Ferdi) => { 541 writeFileSync(
542 filePath,
543 `module.exports = (config, Ferdi) => {
460 // Write your scripts here 544 // Write your scripts here
461 console.log("Hello, World!", config); 545 console.log("Hello, World!", config);
462} 546};
463`); 547`);
464 } 548 }
465 } else { 549 } else {
466 await fs.ensureFile(filePath); 550 ensureFileSync(filePath);
467 } 551 }
468 shell.showItemInFolder(filePath); 552 shell.showItemInFolder(filePath);
469 } 553 }
@@ -474,23 +558,27 @@ export default class ServicesStore extends Store {
474 await request._promise; 558 await request._promise;
475 } 559 }
476 560
477 @action _setActive({ serviceId, keepActiveRoute }) { 561 @action _setActive({ serviceId, keepActiveRoute = null }) {
478 if (!keepActiveRoute) this.stores.router.push('/'); 562 if (!keepActiveRoute) this.stores.router.push('/');
479 const service = this.one(serviceId); 563 const service = this.one(serviceId);
480 564
481 this.all.forEach((s, index) => { 565 this.all.forEach(s => {
482 this.all[index].isActive = false; 566 s.isActive = false;
483 }); 567 });
484 service.isActive = true; 568 service.isActive = true;
485 this._awake({ serviceId: service.id }); 569 this._awake({ serviceId: service.id });
486 service.lastUsed = Date.now();
487 570
488 if (this.isTodosServiceActive && !this.stores.todos.settings.isFeatureEnabledByUser) { 571 if (
572 this.isTodosServiceActive &&
573 !this.stores.todos.settings.isFeatureEnabledByUser
574 ) {
489 this.actions.todos.toggleTodosFeatureVisibility(); 575 this.actions.todos.toggleTodosFeatureVisibility();
490 } 576 }
491 577
492 // Update list of last used services 578 // Update list of last used services
493 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId); 579 this.lastUsedServices = this.lastUsedServices.filter(
580 id => id !== serviceId,
581 );
494 this.lastUsedServices.unshift(serviceId); 582 this.lastUsedServices.unshift(serviceId);
495 583
496 this._focusActiveService(); 584 this._focusActiveService();
@@ -502,7 +590,11 @@ export default class ServicesStore extends Store {
502 } 590 }
503 591
504 @action _setActiveNext() { 592 @action _setActiveNext() {
505 const nextIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), 1, this.allDisplayed.length); 593 const nextIndex = this._wrapIndex(
594 this.allDisplayed.findIndex(service => service.isActive),
595 1,
596 this.allDisplayed.length,
597 );
506 598
507 // TODO: simplify this; 599 // TODO: simplify this;
508 this.all.forEach((s, index) => { 600 this.all.forEach((s, index) => {
@@ -512,7 +604,11 @@ export default class ServicesStore extends Store {
512 } 604 }
513 605
514 @action _setActivePrev() { 606 @action _setActivePrev() {
515 const prevIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), -1, this.allDisplayed.length); 607 const prevIndex = this._wrapIndex(
608 this.allDisplayed.findIndex(service => service.isActive),
609 -1,
610 this.allDisplayed.length,
611 );
516 612
517 // TODO: simplify this; 613 // TODO: simplify this;
518 this.all.forEach((s, index) => { 614 this.all.forEach((s, index) => {
@@ -603,17 +699,21 @@ export default class ServicesStore extends Store {
603 const { options } = args[0]; 699 const { options } = args[0];
604 700
605 // Check if we are in scheduled Do-not-Disturb time 701 // Check if we are in scheduled Do-not-Disturb time
606 const { 702 const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } =
607 scheduledDNDEnabled, 703 this.stores.settings.all.app;
608 scheduledDNDStart,
609 scheduledDNDEnd,
610 } = this.stores.settings.all.app;
611 704
612 if (scheduledDNDEnabled && isInTimeframe(scheduledDNDStart, scheduledDNDEnd)) { 705 if (
706 scheduledDNDEnabled &&
707 isInTimeframe(scheduledDNDStart, scheduledDNDEnd)
708 ) {
613 return; 709 return;
614 } 710 }
615 711
616 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.app.isAppMuted) { 712 if (
713 service.recipe.hasNotificationSound ||
714 service.isMuted ||
715 this.stores.settings.all.app.isAppMuted
716 ) {
617 Object.assign(options, { 717 Object.assign(options, {
618 silent: true, 718 silent: true,
619 }); 719 });
@@ -623,7 +723,8 @@ export default class ServicesStore extends Store {
623 let title = `Notification from ${service.name}`; 723 let title = `Notification from ${service.name}`;
624 if (!this.stores.settings.all.app.privateNotifications) { 724 if (!this.stores.settings.all.app.privateNotifications) {
625 options.body = typeof options.body === 'string' ? options.body : ''; 725 options.body = typeof options.body === 'string' ? options.body : '';
626 title = typeof args[0].title === 'string' ? args[0].title : service.name; 726 title =
727 typeof args[0].title === 'string' ? args[0].title : service.name;
627 } else { 728 } else {
628 // Remove message data from notification in private mode 729 // Remove message data from notification in private mode
629 options.body = ''; 730 options.body = '';
@@ -686,11 +787,13 @@ export default class ServicesStore extends Store {
686 } 787 }
687 788
688 @action _sendIPCMessageToAllServices({ channel, args }) { 789 @action _sendIPCMessageToAllServices({ channel, args }) {
689 this.all.forEach(s => this.actions.service.sendIPCMessage({ 790 this.all.forEach(s =>
690 serviceId: s.id, 791 this.actions.service.sendIPCMessage({
691 channel, 792 serviceId: s.id,
692 args, 793 channel,
693 })); 794 args,
795 }),
796 );
694 } 797 }
695 798
696 @action _openWindow({ event }) { 799 @action _openWindow({ event }) {
@@ -737,9 +840,11 @@ export default class ServicesStore extends Store {
737 } 840 }
738 841
739 @action _reloadAll() { 842 @action _reloadAll() {
740 this.enabled.forEach(s => this._reload({ 843 this.enabled.forEach(s =>
741 serviceId: s.id, 844 this._reload({
742 })); 845 serviceId: s.id,
846 }),
847 );
743 } 848 }
744 849
745 @action _reloadUpdatedServices() { 850 @action _reloadUpdatedServices() {
@@ -758,10 +863,18 @@ export default class ServicesStore extends Store {
758 863
759 @action _reorderService({ oldIndex, newIndex }) { 864 @action _reorderService({ oldIndex, newIndex }) {
760 const { showDisabledServices } = this.stores.settings.all.app; 865 const { showDisabledServices } = this.stores.settings.all.app;
761 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]); 866 const oldEnabledSortIndex = showDisabledServices
762 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]); 867 ? oldIndex
763 868 : this.all.indexOf(this.enabled[oldIndex]);
764 this.all.splice(newEnabledSortIndex, 0, this.all.splice(oldEnabledSortIndex, 1)[0]); 869 const newEnabledSortIndex = showDisabledServices
870 ? newIndex
871 : this.all.indexOf(this.enabled[newIndex]);
872
873 this.all.splice(
874 newEnabledSortIndex,
875 0,
876 this.all.splice(oldEnabledSortIndex, 1)[0],
877 );
765 878
766 const services = {}; 879 const services = {};
767 this.all.forEach((s, index) => { 880 this.all.forEach((s, index) => {
@@ -769,8 +882,8 @@ export default class ServicesStore extends Store {
769 }); 882 });
770 883
771 this.reorderServicesRequest.execute(services); 884 this.reorderServicesRequest.execute(services);
772 this.allServicesRequest.patch((data) => { 885 this.allServicesRequest.patch(data => {
773 data.forEach((s) => { 886 data.forEach(s => {
774 const service = s; 887 const service = s;
775 888
776 service.order = services[s.id]; 889 service.order = services[s.id];
@@ -804,6 +917,18 @@ export default class ServicesStore extends Store {
804 }); 917 });
805 } 918 }
806 919
920 @action _toggleDarkMode({ serviceId }) {
921 const service = this.one(serviceId);
922
923 this.actions.service.updateService({
924 serviceId,
925 serviceData: {
926 isDarkModeEnabled: !service.isDarkModeEnabled,
927 },
928 redirect: false,
929 });
930 }
931
807 @action _openDevTools({ serviceId }) { 932 @action _openDevTools({ serviceId }) {
808 const service = this.one(serviceId); 933 const service = this.one(serviceId);
809 if (service.isTodosService) { 934 if (service.isTodosService) {
@@ -825,26 +950,36 @@ export default class ServicesStore extends Store {
825 950
826 @action _hibernate({ serviceId }) { 951 @action _hibernate({ serviceId }) {
827 const service = this.one(serviceId); 952 const service = this.one(serviceId);
828 if (service.isActive || !service.isHibernationEnabled) { 953 if (!service.canHibernate) {
829 debug('Skipping service hibernation'); 954 return;
955 }
956 if (service.isActive) {
957 debug(`Skipping service hibernation for ${service.name}`);
830 return; 958 return;
831 } 959 }
832 960
833 debug(`Hibernate ${service.name}`); 961 debug(`Hibernate ${service.name}`);
834 962
835 service.isHibernating = true; 963 service.isHibernationRequested = true;
964 service.lastHibernated = Date.now();
836 } 965 }
837 966
838 @action _awake({ serviceId }) { 967 @action _awake({ serviceId }) {
839 const service = this.one(serviceId); 968 const service = this.one(serviceId);
840 service.isHibernating = false; 969 debug(`Waking up from service hibernation for ${service.name}`);
841 service.liveFrom = Date.now(); 970 service.isHibernationRequested = false;
971 service.lastUsed = Date.now();
972 service.lastHibernated = null;
842 } 973 }
843 974
844 @action _resetLastPollTimer({ serviceId = null }) { 975 @action _resetLastPollTimer({ serviceId = null }) {
845 debug(`Reset last poll timer for ${serviceId ? `service: "${serviceId}"` : 'all services'}`); 976 debug(
977 `Reset last poll timer for ${
978 serviceId ? `service: "${serviceId}"` : 'all services'
979 }`,
980 );
846 981
847 const resetTimer = (service) => { 982 const resetTimer = service => {
848 service.lastPollAnswer = Date.now(); 983 service.lastPollAnswer = Date.now();
849 service.lastPoll = Date.now(); 984 service.lastPoll = Date.now();
850 }; 985 };
@@ -884,9 +1019,13 @@ export default class ServicesStore extends Store {
884 _mapActiveServiceToServiceModelReaction() { 1019 _mapActiveServiceToServiceModelReaction() {
885 const { activeService } = this.stores.settings.all.service; 1020 const { activeService } = this.stores.settings.all.service;
886 if (this.allDisplayed.length) { 1021 if (this.allDisplayed.length) {
887 this.allDisplayed.map(service => Object.assign(service, { 1022 this.allDisplayed.map(service =>
888 isActive: activeService ? activeService === service.id : this.allDisplayed[0].id === service.id, 1023 Object.assign(service, {
889 })); 1024 isActive: activeService
1025 ? activeService === service.id
1026 : this.allDisplayed[0].id === service.id,
1027 }),
1028 );
890 } 1029 }
891 } 1030 }
892 1031
@@ -895,12 +1034,23 @@ export default class ServicesStore extends Store {
895 const { showMessageBadgesEvenWhenMuted } = this.stores.ui; 1034 const { showMessageBadgesEvenWhenMuted } = this.stores.ui;
896 1035
897 const unreadDirectMessageCount = this.allDisplayed 1036 const unreadDirectMessageCount = this.allDisplayed
898 .filter(s => (showMessageBadgeWhenMuted || s.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && s.isBadgeEnabled) 1037 .filter(
1038 s =>
1039 (showMessageBadgeWhenMuted || s.isNotificationEnabled) &&
1040 showMessageBadgesEvenWhenMuted &&
1041 s.isBadgeEnabled,
1042 )
899 .map(s => s.unreadDirectMessageCount) 1043 .map(s => s.unreadDirectMessageCount)
900 .reduce((a, b) => a + b, 0); 1044 .reduce((a, b) => a + b, 0);
901 1045
902 const unreadIndirectMessageCount = this.allDisplayed 1046 const unreadIndirectMessageCount = this.allDisplayed
903 .filter(s => (showMessageBadgeWhenMuted && showMessageBadgesEvenWhenMuted) && (s.isBadgeEnabled && s.isIndirectMessageBadgeEnabled)) 1047 .filter(
1048 s =>
1049 showMessageBadgeWhenMuted &&
1050 showMessageBadgesEvenWhenMuted &&
1051 s.isBadgeEnabled &&
1052 s.isIndirectMessageBadgeEnabled,
1053 )
904 .map(s => s.unreadIndirectMessageCount) 1054 .map(s => s.unreadIndirectMessageCount)
905 .reduce((a, b) => a + b, 0); 1055 .reduce((a, b) => a + b, 0);
906 1056
@@ -927,7 +1077,7 @@ export default class ServicesStore extends Store {
927 const { enabled } = this; 1077 const { enabled } = this;
928 const { isAppMuted } = this.stores.settings.app; 1078 const { isAppMuted } = this.stores.settings.app;
929 1079
930 enabled.forEach((service) => { 1080 enabled.forEach(service => {
931 const { isAttached } = service; 1081 const { isAttached } = service;
932 const isMuted = isAppMuted || service.isMuted; 1082 const isMuted = isAppMuted || service.isMuted;
933 1083
@@ -954,48 +1104,30 @@ export default class ServicesStore extends Store {
954 1104
955 if (!recipe) return; 1105 if (!recipe) return;
956 1106
957 if (recipe.hasTeamId && recipe.hasCustomUrl && data.team && data.customUrl) { 1107 if (
1108 recipe.hasTeamId &&
1109 recipe.hasCustomUrl &&
1110 data.team &&
1111 data.customUrl
1112 ) {
958 delete serviceData.team; 1113 delete serviceData.team;
959 } 1114 }
960 1115
961 return serviceData; 1116 return serviceData;
962 } 1117 }
963 1118
964 _restrictServiceAccess() {
965 const { features } = this.stores.features;
966 const { userHasReachedServiceLimit, serviceLimit } = this.stores.serviceLimit;
967
968 this.all.map((service, index) => {
969 if (userHasReachedServiceLimit) {
970 service.isServiceAccessRestricted = index >= serviceLimit;
971
972 if (service.isServiceAccessRestricted) {
973 service.restrictionType = RESTRICTION_TYPES.SERVICE_LIMIT;
974
975 debug('Restricting access to server due to service limit');
976 }
977 }
978
979 if (service.isUsingCustomUrl) {
980 service.isServiceAccessRestricted = !features.isCustomUrlIncludedInCurrentPlan;
981
982 if (service.isServiceAccessRestricted) {
983 service.restrictionType = RESTRICTION_TYPES.CUSTOM_URL;
984
985 debug('Restricting access to server due to custom url');
986 }
987 }
988
989 return service;
990 });
991 }
992
993 _checkForActiveService() { 1119 _checkForActiveService() {
994 if (!this.stores.router.location || this.stores.router.location.pathname.includes('auth/signup')) { 1120 if (
1121 !this.stores.router.location ||
1122 this.stores.router.location.pathname.includes('auth/signup')
1123 ) {
995 return; 1124 return;
996 } 1125 }
997 1126
998 if (this.allDisplayed.findIndex(service => service.isActive) === -1 && this.allDisplayed.length !== 0) { 1127 if (
1128 this.allDisplayed.findIndex(service => service.isActive) === -1 &&
1129 this.allDisplayed.length !== 0
1130 ) {
999 debug('No active service found, setting active service to index 0'); 1131 debug('No active service found, setting active service to index 0');
1000 1132
1001 this._setActive({ serviceId: this.allDisplayed[0].id }); 1133 this._setActive({ serviceId: this.allDisplayed[0].id });
@@ -1008,13 +1140,19 @@ export default class ServicesStore extends Store {
1008 1140
1009 if (service.webview) { 1141 if (service.webview) {
1010 // We need to completely clone the object, otherwise Electron won't be able to send the object via IPC 1142 // We need to completely clone the object, otherwise Electron won't be able to send the object via IPC
1011 const shareWithWebview = JSON.parse(JSON.stringify(service.shareWithWebview)); 1143 const shareWithWebview = JSON.parse(
1144 JSON.stringify(service.shareWithWebview),
1145 );
1012 1146
1013 debug('Initialize recipe', service.recipe.id, service.name); 1147 debug('Initialize recipe', service.recipe.id, service.name);
1014 service.webview.send('initialize-recipe', { 1148 service.webview.send(
1015 ...shareWithWebview, 1149 'initialize-recipe',
1016 franzVersion: app.getVersion(), 1150 {
1017 }, service.recipe); 1151 ...shareWithWebview,
1152 franzVersion: app.getVersion(),
1153 },
1154 service.recipe,
1155 );
1018 } 1156 }
1019 } 1157 }
1020 1158