diff options
Diffstat (limited to 'src/stores/ServicesStore.js')
-rw-r--r-- | src/stores/ServicesStore.js | 452 |
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 @@ | |||
1 | import { shell } from 'electron'; | 1 | import { shell } from 'electron'; |
2 | import { | 2 | import { action, reaction, computed, observable } from 'mobx'; |
3 | action, | ||
4 | reaction, | ||
5 | computed, | ||
6 | observable, | ||
7 | } from 'mobx'; | ||
8 | import { debounce, remove } from 'lodash'; | 3 | import { debounce, remove } from 'lodash'; |
9 | import ms from 'ms'; | 4 | import ms from 'ms'; |
10 | import { app } from '@electron/remote'; | 5 | import { app } from '@electron/remote'; |
11 | import fs from 'fs-extra'; | 6 | import { ensureFileSync, pathExistsSync, writeFileSync } from 'fs-extra'; |
12 | import path from 'path'; | 7 | import { join } from 'path'; |
13 | 8 | ||
14 | import Store from './lib/Store'; | 9 | import Store from './lib/Store'; |
15 | import Request from './lib/Request'; | 10 | import Request from './lib/Request'; |
16 | import CachedRequest from './lib/CachedRequest'; | 11 | import CachedRequest from './lib/CachedRequest'; |
17 | import { matchRoute } from '../helpers/routing-helpers'; | 12 | import { matchRoute } from '../helpers/routing-helpers'; |
18 | import { isInTimeframe } from '../helpers/schedule-helpers'; | 13 | import { isInTimeframe } from '../helpers/schedule-helpers'; |
19 | import { getRecipeDirectory, getDevRecipeDirectory } from '../helpers/recipe-helpers'; | 14 | import { |
15 | getRecipeDirectory, | ||
16 | getDevRecipeDirectory, | ||
17 | } from '../helpers/recipe-helpers'; | ||
20 | import { workspaceStore } from '../features/workspaces'; | 18 | import { workspaceStore } from '../features/workspaces'; |
21 | import { serviceLimitStore } from '../features/serviceLimit'; | ||
22 | import { RESTRICTION_TYPES } from '../models/Service'; | ||
23 | import { KEEP_WS_LOADED_USID } from '../config'; | 19 | import { KEEP_WS_LOADED_USID } from '../config'; |
24 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | 20 | import { 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 | ||