aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores
diff options
context:
space:
mode:
authorLibravatar Amine El Mouafik <412895+kytwb@users.noreply.github.com>2021-02-08 10:34:45 +0100
committerLibravatar GitHub <noreply@github.com>2021-02-08 10:34:45 +0100
commit035002ceedf78d5ec73eabc0df7f06139939b967 (patch)
tree1c0d1e9531bae05fb65d70b9ea25baf404b74fe1 /src/stores
parentdocs: add k0staa as a contributor (#1193) (diff)
downloadferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.tar.gz
ferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.tar.zst
ferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.zip
Synchronize with Franz 5.6.0 (#1033)
Co-authored-by: FranzBot <i18n@meetfranz.com> Co-authored-by: vantezzen <hello@vantezzen.io> Co-authored-by: Makazzz <makazzzpro@live.ca> Co-authored-by: Stefan Malzner <stefan@adlk.io> Co-authored-by: Amine Mouafik <amine@mouafik.fr>
Diffstat (limited to 'src/stores')
-rw-r--r--src/stores/AppStore.js136
-rw-r--r--src/stores/RecipesStore.js2
-rw-r--r--src/stores/ServicesStore.js131
-rw-r--r--src/stores/UserStore.js19
4 files changed, 234 insertions, 54 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 153fdb2c8..869cfa9d6 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -3,7 +3,6 @@ import {
3 action, computed, observable, 3 action, computed, observable,
4} from 'mobx'; 4} from 'mobx';
5import moment from 'moment'; 5import moment from 'moment';
6import { getDoNotDisturb } from '@meetfranz/electron-notification-state';
7import AutoLaunch from 'auto-launch'; 6import AutoLaunch from 'auto-launch';
8import prettyBytes from 'pretty-bytes'; 7import prettyBytes from 'pretty-bytes';
9import ms from 'ms'; 8import ms from 'ms';
@@ -27,7 +26,10 @@ import { sleep } from '../helpers/async-helpers';
27const debug = require('debug')('Ferdi:AppStore'); 26const debug = require('debug')('Ferdi:AppStore');
28 27
29const { 28const {
30 app, nativeTheme, screen, powerMonitor, 29 app,
30 screen,
31 powerMonitor,
32 nativeTheme,
31} = remote; 33} = remote;
32 34
33const mainWindow = remote.getCurrentWindow(); 35const mainWindow = remote.getCurrentWindow();
@@ -63,7 +65,7 @@ export default class AppStore extends Store {
63 65
64 @observable authRequestFailed = false; 66 @observable authRequestFailed = false;
65 67
66 @observable timeSuspensionStart; 68 @observable timeSuspensionStart = moment();
67 69
68 @observable timeOfflineStart; 70 @observable timeOfflineStart;
69 71
@@ -118,11 +120,19 @@ export default class AppStore extends Store {
118 window.addEventListener('focus', this.actions.service.focusActiveService); 120 window.addEventListener('focus', this.actions.service.focusActiveService);
119 121
120 // Online/Offline handling 122 // Online/Offline handling
121 window.addEventListener('online', () => { this.isOnline = true; }); 123 window.addEventListener('online', () => {
122 window.addEventListener('offline', () => { this.isOnline = false; }); 124 this.isOnline = true;
125 });
126 window.addEventListener('offline', () => {
127 this.isOnline = false;
128 });
123 129
124 mainWindow.on('enter-full-screen', () => { this.isFullScreen = true; }); 130 mainWindow.on('enter-full-screen', () => {
125 mainWindow.on('leave-full-screen', () => { this.isFullScreen = false; }); 131 this.isFullScreen = true;
132 });
133 mainWindow.on('leave-full-screen', () => {
134 this.isFullScreen = false;
135 });
126 136
127 137
128 this.isOnline = navigator.onLine; 138 this.isOnline = navigator.onLine;
@@ -137,10 +147,16 @@ export default class AppStore extends Store {
137 setInterval(() => this._systemDND(), ms('5s')); 147 setInterval(() => this._systemDND(), ms('5s'));
138 148
139 this.fetchDataInterval = setInterval(() => { 149 this.fetchDataInterval = setInterval(() => {
140 this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); 150 this.stores.user.getUserInfoRequest.invalidate({
141 this.stores.features.featuresRequest.invalidate({ immediately: true }); 151 immediately: true,
142 this.stores.news.latestNewsRequest.invalidate({ immediately: true }); 152 });
143 }, ms('10m')); 153 this.stores.features.featuresRequest.invalidate({
154 immediately: true,
155 });
156 this.stores.news.latestNewsRequest.invalidate({
157 immediately: true,
158 });
159 }, ms('60m'));
144 160
145 // Check for updates once every 4 hours 161 // Check for updates once every 4 hours
146 setInterval(() => this._checkForUpdates(), CHECK_INTERVAL); 162 setInterval(() => this._checkForUpdates(), CHECK_INTERVAL);
@@ -174,7 +190,9 @@ export default class AppStore extends Store {
174 // Handle deep linking (franz://) 190 // Handle deep linking (franz://)
175 ipcRenderer.on('navigateFromDeepLink', (event, data) => { 191 ipcRenderer.on('navigateFromDeepLink', (event, data) => {
176 debug('Navigate from deep link', data); 192 debug('Navigate from deep link', data);
177 let { url } = data; 193 let {
194 url,
195 } = data;
178 if (!url) return; 196 if (!url) return;
179 197
180 url = url.replace(/\/$/, ''); 198 url = url.replace(/\/$/, '');
@@ -207,13 +225,17 @@ export default class AppStore extends Store {
207 }); 225 });
208 226
209 powerMonitor.on('resume', () => { 227 powerMonitor.on('resume', () => {
210 debug('System resumed, last suspended on', this.timeSuspensionStart.toString()); 228 debug('System resumed, last suspended on', this.timeSuspensionStart);
229 this.actions.service.resetLastPollTimer();
211 230
212 if (this.timeSuspensionStart.add(10, 'm').isBefore(moment()) && this.stores.settings.app.get('reloadAfterResume')) { 231 if (this.timeSuspensionStart.add(10, 'm').isBefore(moment()) && this.stores.settings.app.get('reloadAfterResume')) {
213 debug('Reloading services, user info and features'); 232 debug('Reloading services, user info and features');
214 233
215 setTimeout(() => { 234 setInterval(() => {
216 window.location.reload(); 235 debug('Reload app interval is starting');
236 if (this.isOnline) {
237 window.location.reload();
238 }
217 }, ms('2s')); 239 }, ms('2s'));
218 } 240 }
219 }); 241 });
@@ -251,8 +273,14 @@ export default class AppStore extends Store {
251 ferdi: { 273 ferdi: {
252 version: app.getVersion(), 274 version: app.getVersion(),
253 electron: process.versions.electron, 275 electron: process.versions.electron,
254 installedRecipes: this.stores.recipes.all.map(recipe => ({ id: recipe.id, version: recipe.version })), 276 installedRecipes: this.stores.recipes.all.map(recipe => ({
255 devRecipes: this.stores.recipePreviews.dev.map(recipe => ({ id: recipe.id, version: recipe.version })), 277 id: recipe.id,
278 version: recipe.version,
279 })),
280 devRecipes: this.stores.recipePreviews.dev.map(recipe => ({
281 id: recipe.id,
282 version: recipe.version,
283 })),
256 services: this.stores.services.all.map(service => ({ 284 services: this.stores.services.all.map(service => ({
257 id: service.id, 285 id: service.id,
258 recipe: service.recipe.id, 286 recipe: service.recipe.id,
@@ -264,7 +292,10 @@ export default class AppStore extends Store {
264 isDarkModeEnabled: service.isDarkModeEnabled, 292 isDarkModeEnabled: service.isDarkModeEnabled,
265 })), 293 })),
266 messages: this.stores.globalError.messages, 294 messages: this.stores.globalError.messages,
267 workspaces: this.stores.workspaces.workspaces.map(workspace => ({ id: workspace.id, services: workspace.services })), 295 workspaces: this.stores.workspaces.workspaces.map(workspace => ({
296 id: workspace.id,
297 services: workspace.services,
298 })),
268 windowSettings: readJsonSync(path.join(app.getPath('userData'), 'window-state.json')), 299 windowSettings: readJsonSync(path.join(app.getPath('userData'), 'window-state.json')),
269 settings, 300 settings,
270 features: this.stores.features.features, 301 features: this.stores.features.features,
@@ -275,7 +306,10 @@ export default class AppStore extends Store {
275 306
276 // Actions 307 // Actions
277 @action _notify({ 308 @action _notify({
278 title, options, notificationId, serviceId = null, 309 title,
310 options,
311 notificationId,
312 serviceId = null,
279 }) { 313 }) {
280 if (this.stores.settings.all.app.isAppMuted) return; 314 if (this.stores.settings.all.app.isAppMuted) return;
281 315
@@ -288,15 +322,17 @@ export default class AppStore extends Store {
288 322
289 debug('New notification', title, options); 323 debug('New notification', title, options);
290 324
291 notification.onclick = (e) => { 325 notification.onclick = () => {
292 if (serviceId) { 326 if (serviceId) {
293 this.actions.service.sendIPCMessage({ 327 this.actions.service.sendIPCMessage({
294 channel: `notification-onclick:${notificationId}`, 328 channel: `notification-onclick:${notificationId}`,
295 args: e, 329 args: {},
296 serviceId, 330 serviceId,
297 }); 331 });
298 332
299 this.actions.service.setActive({ serviceId }); 333 this.actions.service.setActive({
334 serviceId,
335 });
300 mainWindow.show(); 336 mainWindow.show();
301 if (app.mainWindow.isMinimized()) { 337 if (app.mainWindow.isMinimized()) {
302 mainWindow.restore(); 338 mainWindow.restore();
@@ -308,7 +344,10 @@ export default class AppStore extends Store {
308 }; 344 };
309 } 345 }
310 346
311 @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) { 347 @action _setBadge({
348 unreadDirectMessageCount,
349 unreadIndirectMessageCount,
350 }) {
312 let indicator = unreadDirectMessageCount; 351 let indicator = unreadDirectMessageCount;
313 352
314 if (indicator === 0 && unreadIndirectMessageCount !== 0) { 353 if (indicator === 0 && unreadIndirectMessageCount !== 0) {
@@ -319,10 +358,14 @@ export default class AppStore extends Store {
319 indicator = parseInt(indicator, 10); 358 indicator = parseInt(indicator, 10);
320 } 359 }
321 360
322 ipcRenderer.send('updateAppIndicator', { indicator }); 361 ipcRenderer.send('updateAppIndicator', {
362 indicator,
363 });
323 } 364 }
324 365
325 @action _launchOnStartup({ enable }) { 366 @action _launchOnStartup({
367 enable,
368 }) {
326 this.autoLaunchOnStart = enable; 369 this.autoLaunchOnStart = enable;
327 370
328 try { 371 try {
@@ -338,7 +381,9 @@ export default class AppStore extends Store {
338 } 381 }
339 } 382 }
340 383
341 @action _openExternalUrl({ url }) { 384 @action _openExternalUrl({
385 url,
386 }) {
342 const parsedUrl = new URL(url); 387 const parsedUrl = new URL(url);
343 debug('open external url', parsedUrl); 388 debug('open external url', parsedUrl);
344 389
@@ -348,14 +393,20 @@ export default class AppStore extends Store {
348 } 393 }
349 394
350 @action _checkForUpdates() { 395 @action _checkForUpdates() {
351 this.updateStatus = this.updateStatusTypes.CHECKING; 396 if (this.isOnline) {
352 ipcRenderer.send('autoUpdate', { action: 'check' }); 397 this.updateStatus = this.updateStatusTypes.CHECKING;
398 ipcRenderer.send('autoUpdate', {
399 action: 'check',
400 });
353 401
354 this.actions.recipe.update(); 402 this.actions.recipe.update();
403 }
355 } 404 }
356 405
357 @action _installUpdate() { 406 @action _installUpdate() {
358 ipcRenderer.send('autoUpdate', { action: 'install' }); 407 ipcRenderer.send('autoUpdate', {
408 action: 'install',
409 });
359 } 410 }
360 411
361 @action _resetUpdateStatus() { 412 @action _resetUpdateStatus() {
@@ -366,7 +417,10 @@ export default class AppStore extends Store {
366 this.healthCheckRequest.execute(); 417 this.healthCheckRequest.execute();
367 } 418 }
368 419
369 @action _muteApp({ isMuted, overrideSystemMute = true }) { 420 @action _muteApp({
421 isMuted,
422 overrideSystemMute = true,
423 }) {
370 this.isSystemMuteOverridden = overrideSystemMute; 424 this.isSystemMuteOverridden = overrideSystemMute;
371 this.actions.settings.update({ 425 this.actions.settings.update({
372 type: 'app', 426 type: 'app',
@@ -377,7 +431,9 @@ export default class AppStore extends Store {
377 } 431 }
378 432
379 @action _toggleMuteApp() { 433 @action _toggleMuteApp() {
380 this._muteApp({ isMuted: !this.stores.settings.all.app.isAppMuted }); 434 this._muteApp({
435 isMuted: !this.stores.settings.all.app.isAppMuted,
436 });
381 } 437 }
382 438
383 @action async _clearAllCache() { 439 @action async _clearAllCache() {
@@ -391,7 +447,9 @@ export default class AppStore extends Store {
391 } catch (ex) { 447 } catch (ex) {
392 console.log('Error while deleting service partition directory - ', ex); 448 console.log('Error while deleting service partition directory - ', ex);
393 } 449 }
394 await Promise.all(this.stores.services.all.map(s => this.actions.service.clearCache({ serviceId: s.id }))); 450 await Promise.all(this.stores.services.all.map(s => this.actions.service.clearCache({
451 serviceId: s.id,
452 })));
395 453
396 await clearAppCache._promise; 454 await clearAppCache._promise;
397 455
@@ -446,7 +504,10 @@ export default class AppStore extends Store {
446 const { showMessageBadgesEvenWhenMuted } = this.stores.ui; 504 const { showMessageBadgesEvenWhenMuted } = this.stores.ui;
447 505
448 if (!showMessageBadgesEvenWhenMuted) { 506 if (!showMessageBadgesEvenWhenMuted) {
449 this.actions.app.setBadge({ unreadDirectMessageCount: 0, unreadIndirectMessageCount: 0 }); 507 this.actions.app.setBadge({
508 unreadDirectMessageCount: 0,
509 unreadIndirectMessageCount: 0,
510 });
450 } 511 }
451 } 512 }
452 513
@@ -491,8 +552,11 @@ export default class AppStore extends Store {
491 return autoLauncher.isEnabled() || false; 552 return autoLauncher.isEnabled() || false;
492 } 553 }
493 554
494 _systemDND() { 555 async _systemDND() {
495 const dnd = getDoNotDisturb(); 556 debug('Checking if Do Not Disturb Mode is on');
557 const dnd = await ipcRenderer.invoke('get-dnd');
558 debug('Do not disturb mode is', dnd);
559 // ipcRenderer.on('autoUpdate', (event, data) => {
496 if (dnd !== this.stores.settings.all.app.isAppMuted && !this.isSystemMuteOverridden) { 560 if (dnd !== this.stores.settings.all.app.isAppMuted && !this.isSystemMuteOverridden) {
497 this.actions.app.muteApp({ 561 this.actions.app.muteApp({
498 isMuted: dnd, 562 isMuted: dnd,
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
index cf5d0a074..965aa3a0a 100644
--- a/src/stores/RecipesStore.js
+++ b/src/stores/RecipesStore.js
@@ -66,7 +66,7 @@ export default class RecipesStore extends Store {
66 } 66 }
67 67
68 // Actions 68 // Actions
69 @action async _install({ recipeId }) { 69 async _install({ recipeId }) {
70 const recipe = await this.installRecipeRequest.execute(recipeId)._promise; 70 const recipe = await this.installRecipeRequest.execute(recipeId)._promise;
71 await this.allRecipesRequest.invalidate({ immediately: true })._promise; 71 await this.allRecipesRequest.invalidate({ immediately: true })._promise;
72 72
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);
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index c1ed2944a..7b4d39524 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -3,6 +3,7 @@ import moment from 'moment';
3import jwt from 'jsonwebtoken'; 3import jwt from 'jsonwebtoken';
4import localStorage from 'mobx-localstorage'; 4import localStorage from 'mobx-localstorage';
5import ms from 'ms'; 5import ms from 'ms';
6import { remote } from 'electron';
6 7
7import { isDevMode } from '../environment'; 8import { isDevMode } from '../environment';
8import Store from './lib/Store'; 9import Store from './lib/Store';
@@ -11,6 +12,9 @@ import CachedRequest from './lib/CachedRequest';
11import { sleep } from '../helpers/async-helpers'; 12import { sleep } from '../helpers/async-helpers';
12import { getPlan } from '../helpers/plan-helpers'; 13import { getPlan } from '../helpers/plan-helpers';
13import { PLANS } from '../config'; 14import { PLANS } from '../config';
15import { TODOS_PARTITION_ID } from '../features/todos';
16
17const { session } = remote;
14 18
15const debug = require('debug')('Ferdi:UserStore'); 19const debug = require('debug')('Ferdi:UserStore');
16 20
@@ -28,6 +32,8 @@ export default class UserStore extends Store {
28 32
29 PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`; 33 PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`;
30 34
35 SETUP_ROUTE = `${this.BASE_ROUTE}/signup/setup`;
36
31 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`; 37 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`;
32 38
33 INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`; 39 INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`;
@@ -127,6 +133,10 @@ export default class UserStore extends Store {
127 return this.PRICING_ROUTE; 133 return this.PRICING_ROUTE;
128 } 134 }
129 135
136 get setupRoute() {
137 return this.SETUP_ROUTE;
138 }
139
130 get inviteRoute() { 140 get inviteRoute() {
131 return this.INVITE_ROUTE; 141 return this.INVITE_ROUTE;
132 } 142 }
@@ -227,7 +237,7 @@ export default class UserStore extends Store {
227 237
228 this._setUserData(authToken); 238 this._setUserData(authToken);
229 239
230 this.stores.router.push('/'); 240 this.stores.router.push(this.SETUP_ROUTE);
231 } 241 }
232 242
233 @action async _retrievePassword({ email }) { 243 @action async _retrievePassword({ email }) {
@@ -285,6 +295,13 @@ export default class UserStore extends Store {
285 295
286 this.getUserInfoRequest.invalidate().reset(); 296 this.getUserInfoRequest.invalidate().reset();
287 this.authToken = null; 297 this.authToken = null;
298
299 this.stores.services.allServicesRequest.invalidate().reset();
300
301 if (this.stores.todos.isTodosEnabled) {
302 const sess = session.fromPartition(TODOS_PARTITION_ID);
303 sess.clearStorageData();
304 }
288 } 305 }
289 306
290 @action async _importLegacyServices({ services }) { 307 @action async _importLegacyServices({ services }) {