aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores')
-rw-r--r--src/stores/AppStore.js32
-rw-r--r--src/stores/FeaturesStore.js16
-rw-r--r--src/stores/GlobalErrorStore.js10
-rw-r--r--src/stores/RecipesStore.js51
-rw-r--r--src/stores/RequestStore.js18
-rw-r--r--src/stores/ServicesStore.js291
-rw-r--r--src/stores/SettingsStore.js60
-rw-r--r--src/stores/UserStore.js78
-rw-r--r--src/stores/index.ts4
-rw-r--r--src/stores/lib/CachedRequest.js96
-rw-r--r--src/stores/lib/Request.js2
-rw-r--r--src/stores/lib/Store.js6
12 files changed, 365 insertions, 299 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 469e7519e..85f74a91e 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -251,16 +251,14 @@ export default class AppStore extends Store {
251 // macOS catalina notifications hack 251 // macOS catalina notifications hack
252 // notifications got stuck after upgrade but forcing a notification 252 // notifications got stuck after upgrade but forcing a notification
253 // via `new Notification` triggered the permission request 253 // via `new Notification` triggered the permission request
254 if (isMac) { 254 if (isMac && !localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) {
255 if (!localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) { 255 debug('Triggering macOS Catalina notification permission trigger');
256 debug('Triggering macOS Catalina notification permission trigger'); 256 // eslint-disable-next-line no-new
257 // eslint-disable-next-line no-new 257 new window.Notification('Welcome to Ferdi 5', {
258 new window.Notification('Welcome to Ferdi 5', { 258 body: 'Have a wonderful day & happy messaging.',
259 body: 'Have a wonderful day & happy messaging.', 259 });
260 });
261 260
262 localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true); 261 localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true);
263 }
264 } 262 }
265 } 263 }
266 264
@@ -325,7 +323,7 @@ export default class AppStore extends Store {
325 323
326 debug('New notification', title, options); 324 debug('New notification', title, options);
327 325
328 notification.onclick = () => { 326 notification.addEventListener('click', () => {
329 if (serviceId) { 327 if (serviceId) {
330 this.actions.service.sendIPCMessage({ 328 this.actions.service.sendIPCMessage({
331 channel: `notification-onclick:${notificationId}`, 329 channel: `notification-onclick:${notificationId}`,
@@ -346,7 +344,7 @@ export default class AppStore extends Store {
346 344
347 debug('Notification click handler'); 345 debug('Notification click handler');
348 } 346 }
349 }; 347 });
350 } 348 }
351 349
352 @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) { 350 @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) {
@@ -360,7 +358,7 @@ export default class AppStore extends Store {
360 ) { 358 ) {
361 indicator = 0; 359 indicator = 0;
362 } else { 360 } else {
363 indicator = parseInt(indicator, 10); 361 indicator = Number.parseInt(indicator, 10);
364 } 362 }
365 363
366 ipcRenderer.send('updateAppIndicator', { 364 ipcRenderer.send('updateAppIndicator', {
@@ -379,8 +377,8 @@ export default class AppStore extends Store {
379 debug('disabling launch on startup'); 377 debug('disabling launch on startup');
380 autoLauncher.disable(); 378 autoLauncher.disable();
381 } 379 }
382 } catch (err) { 380 } catch (error) {
383 console.warn(err); 381 console.warn(error);
384 } 382 }
385 } 383 }
386 384
@@ -438,7 +436,7 @@ export default class AppStore extends Store {
438 const allServiceIds = await getServiceIdsFromPartitions(); 436 const allServiceIds = await getServiceIdsFromPartitions();
439 const allOrphanedServiceIds = allServiceIds.filter( 437 const allOrphanedServiceIds = allServiceIds.filter(
440 id => 438 id =>
441 !this.stores.services.all.find( 439 !this.stores.services.all.some(
442 s => id.replace('service-', '') === s.id, 440 s => id.replace('service-', '') === s.id,
443 ), 441 ),
444 ); 442 );
@@ -447,8 +445,8 @@ export default class AppStore extends Store {
447 await Promise.all( 445 await Promise.all(
448 allOrphanedServiceIds.map(id => removeServicePartitionDirectory(id)), 446 allOrphanedServiceIds.map(id => removeServicePartitionDirectory(id)),
449 ); 447 );
450 } catch (ex) { 448 } catch (error) {
451 console.log('Error while deleting service partition directory - ', ex); 449 console.log('Error while deleting service partition directory -', error);
452 } 450 }
453 await Promise.all( 451 await Promise.all(
454 this.stores.services.all.map(s => 452 this.stores.services.all.map(s =>
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index 1d50dd714..8e0134d7f 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -51,7 +51,9 @@ export default class FeaturesStore extends Store {
51 let requestResult = {}; 51 let requestResult = {};
52 try { 52 try {
53 requestResult = this.featuresRequest.execute().result; 53 requestResult = this.featuresRequest.execute().result;
54 } catch (e) {} // eslint-disable-line no-empty 54 } catch (error) {
55 console.error(error);
56 }
55 Object.assign(features, requestResult); 57 Object.assign(features, requestResult);
56 } 58 }
57 runInAction('FeaturesStore::_updateFeatures', () => { 59 runInAction('FeaturesStore::_updateFeatures', () => {
@@ -69,15 +71,15 @@ export default class FeaturesStore extends Store {
69 } 71 }
70 72
71 _setupFeatures() { 73 _setupFeatures() {
72 serviceProxy(this.stores, this.actions); 74 serviceProxy(this.stores);
73 basicAuth(this.stores, this.actions); 75 basicAuth();
74 workspaces(this.stores, this.actions); 76 workspaces(this.stores, this.actions);
75 quickSwitch(this.stores, this.actions); 77 quickSwitch();
76 nightlyBuilds(this.stores, this.actions); 78 nightlyBuilds();
77 publishDebugInfo(this.stores, this.actions); 79 publishDebugInfo();
78 settingsWS(this.stores, this.actions); 80 settingsWS(this.stores, this.actions);
79 communityRecipes(this.stores, this.actions); 81 communityRecipes(this.stores, this.actions);
80 todos(this.stores, this.actions); 82 todos(this.stores, this.actions);
81 appearance(this.stores, this.actions); 83 appearance(this.stores);
82 } 84 }
83} 85}
diff --git a/src/stores/GlobalErrorStore.js b/src/stores/GlobalErrorStore.js
index aacaa247f..7cbfdc608 100644
--- a/src/stores/GlobalErrorStore.js
+++ b/src/stores/GlobalErrorStore.js
@@ -12,9 +12,9 @@ export default class GlobalErrorStore extends Store {
12 constructor(...args) { 12 constructor(...args) {
13 super(...args); 13 super(...args);
14 14
15 window.onerror = (...errorArgs) => { 15 window.addEventListener('error', (...errorArgs) => {
16 this._handleConsoleError.call(this, ['error', ...errorArgs]); 16 this._handleConsoleError.call(this, ['error', ...errorArgs]);
17 }; 17 });
18 18
19 const origConsoleError = console.error; 19 const origConsoleError = console.error;
20 window.console.error = (...errorArgs) => { 20 window.console.error = (...errorArgs) => {
@@ -38,7 +38,7 @@ export default class GlobalErrorStore extends Store {
38 } 38 }
39 39
40 _handleConsoleError(type, error, url, line) { 40 _handleConsoleError(type, error, url, line) {
41 if (typeof type === 'object' && type.length && type.length >= 1) { 41 if (typeof type === 'object' && type.length > 0) {
42 this.messages.push({ 42 this.messages.push({
43 type: type[0], 43 type: type[0],
44 info: type, 44 info: type,
@@ -53,14 +53,14 @@ export default class GlobalErrorStore extends Store {
53 } 53 }
54 } 54 }
55 55
56 _handleRequests = action(async (request) => { 56 _handleRequests = action(async request => {
57 if (request.isError) { 57 if (request.isError) {
58 this.error = request.error; 58 this.error = request.error;
59 59
60 if (request.error.json) { 60 if (request.error.json) {
61 try { 61 try {
62 this.response = await request.error.json(); 62 this.response = await request.error.json();
63 } catch (error) { 63 } catch {
64 this.response = {}; 64 this.response = {};
65 } 65 }
66 if (this.error.status === 401) { 66 if (this.error.status === 401) {
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
index d2acebb75..bfbdc57a8 100644
--- a/src/stores/RecipesStore.js
+++ b/src/stores/RecipesStore.js
@@ -25,9 +25,7 @@ export default class RecipesStore extends Store {
25 this.actions.recipe.update.listen(this._update.bind(this)); 25 this.actions.recipe.update.listen(this._update.bind(this));
26 26
27 // Reactions 27 // Reactions
28 this.registerReactions([ 28 this.registerReactions([this._checkIfRecipeIsInstalled.bind(this)]);
29 this._checkIfRecipeIsInstalled.bind(this),
30 ]);
31 } 29 }
32 30
33 setup() { 31 setup() {
@@ -39,7 +37,10 @@ export default class RecipesStore extends Store {
39 } 37 }
40 38
41 @computed get active() { 39 @computed get active() {
42 const match = matchRoute('/settings/services/add/:id', this.stores.router.location.pathname); 40 const match = matchRoute(
41 '/settings/services/add/:id',
42 this.stores.router.location.pathname,
43 );
43 if (match) { 44 if (match) {
44 const activeRecipe = this.one(match.id); 45 const activeRecipe = this.one(match.id);
45 if (activeRecipe) { 46 if (activeRecipe) {
@@ -53,11 +54,11 @@ export default class RecipesStore extends Store {
53 } 54 }
54 55
55 @computed get recipeIdForServices() { 56 @computed get recipeIdForServices() {
56 return this.stores.services.all.map((s) => s.recipe.id); 57 return this.stores.services.all.map(s => s.recipe.id);
57 } 58 }
58 59
59 one(id) { 60 one(id) {
60 return this.all.find((recipe) => recipe.id === id); 61 return this.all.find(recipe => recipe.id === id);
61 } 62 }
62 63
63 isInstalled(id) { 64 isInstalled(id) {
@@ -77,41 +78,43 @@ export default class RecipesStore extends Store {
77 const recipes = {}; 78 const recipes = {};
78 79
79 // Hackfix, reference this.all to fetch services 80 // Hackfix, reference this.all to fetch services
80 debug(`Check Recipe updates for ${this.all.map((recipe) => recipe.id)}`); 81 debug(`Check Recipe updates for ${this.all.map(recipe => recipe.id)}`);
81 82
82 recipeIds.forEach((r) => { 83 for (const r of recipeIds) {
83 const recipe = this.one(r); 84 const recipe = this.one(r);
84 recipes[r] = recipe.version; 85 recipes[r] = recipe.version;
85 }); 86 }
86 87
87 if (Object.keys(recipes).length === 0) return; 88 if (Object.keys(recipes).length === 0) return;
88 89
89 const remoteUpdates = await this.getRecipeUpdatesRequest.execute(recipes)._promise; 90 const remoteUpdates = await this.getRecipeUpdatesRequest.execute(recipes)
91 ._promise;
90 92
91 // Check for local updates 93 // Check for local updates
92 const allJsonFile = asarRecipesPath('all.json'); 94 const allJsonFile = asarRecipesPath('all.json');
93 const allJson = readJSONSync(allJsonFile); 95 const allJson = readJSONSync(allJsonFile);
94 const localUpdates = []; 96 const localUpdates = [];
95 97
96 Object.keys(recipes).forEach((recipe) => { 98 for (const recipe of Object.keys(recipes)) {
97 const version = recipes[recipe]; 99 const version = recipes[recipe];
98 100
99 // Find recipe in local recipe repository 101 // Find recipe in local recipe repository
100 const localRecipe = allJson.find((r) => r.id === recipe); 102 const localRecipe = allJson.find(r => r.id === recipe);
101 103
102 if (localRecipe && semver.lt(version, localRecipe.version)) { 104 if (localRecipe && semver.lt(version, localRecipe.version)) {
103 localUpdates.push(recipe); 105 localUpdates.push(recipe);
104 } 106 }
105 }); 107 }
106 108
107 const updates = [ 109 const updates = [...remoteUpdates, ...localUpdates];
108 ...remoteUpdates, 110 debug(
109 ...localUpdates, 111 'Got update information (local, remote):',
110 ]; 112 localUpdates,
111 debug('Got update information (local, remote):', localUpdates, remoteUpdates); 113 remoteUpdates,
114 );
112 115
113 const length = updates.length - 1; 116 const length = updates.length - 1;
114 const syncUpdate = async (i) => { 117 const syncUpdate = async i => {
115 const update = updates[i]; 118 const update = updates[i];
116 119
117 this.actions.recipe.install({ recipeId: update }); 120 this.actions.recipe.install({ recipeId: update });
@@ -134,7 +137,9 @@ export default class RecipesStore extends Store {
134 async _checkIfRecipeIsInstalled() { 137 async _checkIfRecipeIsInstalled() {
135 const { router } = this.stores; 138 const { router } = this.stores;
136 139
137 const match = router.location && matchRoute('/settings/services/add/:id', router.location.pathname); 140 const match =
141 router.location &&
142 matchRoute('/settings/services/add/:id', router.location.pathname);
138 if (match) { 143 if (match) {
139 const recipeId = match.id; 144 const recipeId = match.id;
140 145
@@ -142,9 +147,11 @@ export default class RecipesStore extends Store {
142 router.push('/settings/recipes'); 147 router.push('/settings/recipes');
143 debug(`Recipe ${recipeId} is not installed, trying to install it`); 148 debug(`Recipe ${recipeId} is not installed, trying to install it`);
144 149
145 const recipe = await this.installRecipeRequest.execute(recipeId)._promise; 150 const recipe = await this.installRecipeRequest.execute(recipeId)
151 ._promise;
146 if (recipe) { 152 if (recipe) {
147 await this.allRecipesRequest.invalidate({ immediately: true })._promise; 153 await this.allRecipesRequest.invalidate({ immediately: true })
154 ._promise;
148 router.push(`/settings/services/add/${recipeId}`); 155 router.push(`/settings/services/add/${recipeId}`);
149 } else { 156 } else {
150 router.push('/settings/recipes'); 157 router.push('/settings/recipes');
diff --git a/src/stores/RequestStore.js b/src/stores/RequestStore.js
index a92f4c685..6d2f2ef91 100644
--- a/src/stores/RequestStore.js
+++ b/src/stores/RequestStore.js
@@ -13,7 +13,7 @@ export default class RequestStore extends Store {
13 13
14 @observable showRequiredRequestsError = false; 14 @observable showRequiredRequestsError = false;
15 15
16 @observable localServerPort = 45569; 16 @observable localServerPort = 45_569;
17 17
18 retries = 0; 18 retries = 0;
19 19
@@ -22,11 +22,11 @@ export default class RequestStore extends Store {
22 constructor(...args) { 22 constructor(...args) {
23 super(...args); 23 super(...args);
24 24
25 this.actions.requests.retryRequiredRequests.listen(this._retryRequiredRequests.bind(this)); 25 this.actions.requests.retryRequiredRequests.listen(
26 this._retryRequiredRequests.bind(this),
27 );
26 28
27 this.registerReactions([ 29 this.registerReactions([this._autoRetry.bind(this)]);
28 this._autoRetry.bind(this),
29 ]);
30 } 30 }
31 31
32 setup() { 32 setup() {
@@ -41,13 +41,11 @@ export default class RequestStore extends Store {
41 } 41 }
42 42
43 @computed get areRequiredRequestsSuccessful() { 43 @computed get areRequiredRequestsSuccessful() {
44 return !this.userInfoRequest.isError 44 return !this.userInfoRequest.isError && !this.servicesRequest.isError;
45 && !this.servicesRequest.isError;
46 } 45 }
47 46
48 @computed get areRequiredRequestsLoading() { 47 @computed get areRequiredRequestsLoading() {
49 return this.userInfoRequest.isExecuting 48 return this.userInfoRequest.isExecuting || this.servicesRequest.isExecuting;
50 || this.servicesRequest.isExecuting;
51 } 49 }
52 50
53 @action _retryRequiredRequests() { 51 @action _retryRequiredRequests() {
@@ -67,7 +65,7 @@ export default class RequestStore extends Store {
67 } 65 }
68 66
69 this._autoRetry(); 67 this._autoRetry();
70 debug(`Retry required requests delayed in ${(delay) / 1000}s`); 68 debug(`Retry required requests delayed in ${delay / 1000}s`);
71 }, delay); 69 }, delay);
72 } 70 }
73 } 71 }
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 75bc71388..67fd4103f 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -10,7 +10,10 @@ import Request from './lib/Request';
10import CachedRequest from './lib/CachedRequest'; 10import CachedRequest from './lib/CachedRequest';
11import { matchRoute } from '../helpers/routing-helpers'; 11import { matchRoute } from '../helpers/routing-helpers';
12import { isInTimeframe } from '../helpers/schedule-helpers'; 12import { isInTimeframe } from '../helpers/schedule-helpers';
13import { getRecipeDirectory, getDevRecipeDirectory } from '../helpers/recipe-helpers'; 13import {
14 getRecipeDirectory,
15 getDevRecipeDirectory,
16} from '../helpers/recipe-helpers';
14import { workspaceStore } from '../features/workspaces'; 17import { workspaceStore } from '../features/workspaces';
15import { KEEP_WS_LOADED_USID } from '../config'; 18import { KEEP_WS_LOADED_USID } from '../config';
16import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 19import { SPELLCHECKER_LOCALES } from '../i18n/languages';
@@ -125,63 +128,49 @@ export default class ServicesStore extends Store {
125 setup() { 128 setup() {
126 // Single key reactions for the sake of your CPU 129 // Single key reactions for the sake of your CPU
127 reaction( 130 reaction(
128 () => ( 131 () => this.stores.settings.app.enableSpellchecking,
129 this.stores.settings.app.enableSpellchecking
130 ),
131 () => { 132 () => {
132 this._shareSettingsWithServiceProcess(); 133 this._shareSettingsWithServiceProcess();
133 }, 134 },
134 ); 135 );
135 136
136 reaction( 137 reaction(
137 () => ( 138 () => this.stores.settings.app.spellcheckerLanguage,
138 this.stores.settings.app.spellcheckerLanguage
139 ),
140 () => { 139 () => {
141 this._shareSettingsWithServiceProcess(); 140 this._shareSettingsWithServiceProcess();
142 }, 141 },
143 ); 142 );
144 143
145 reaction( 144 reaction(
146 () => ( 145 () => this.stores.settings.app.darkMode,
147 this.stores.settings.app.darkMode
148 ),
149 () => { 146 () => {
150 this._shareSettingsWithServiceProcess(); 147 this._shareSettingsWithServiceProcess();
151 }, 148 },
152 ); 149 );
153 150
154 reaction( 151 reaction(
155 () => ( 152 () => this.stores.settings.app.adaptableDarkMode,
156 this.stores.settings.app.adaptableDarkMode
157 ),
158 () => { 153 () => {
159 this._shareSettingsWithServiceProcess(); 154 this._shareSettingsWithServiceProcess();
160 }, 155 },
161 ); 156 );
162 157
163 reaction( 158 reaction(
164 () => ( 159 () => this.stores.settings.app.universalDarkMode,
165 this.stores.settings.app.universalDarkMode
166 ),
167 () => { 160 () => {
168 this._shareSettingsWithServiceProcess(); 161 this._shareSettingsWithServiceProcess();
169 }, 162 },
170 ); 163 );
171 164
172 reaction( 165 reaction(
173 () => ( 166 () => this.stores.settings.app.searchEngine,
174 this.stores.settings.app.searchEngine
175 ),
176 () => { 167 () => {
177 this._shareSettingsWithServiceProcess(); 168 this._shareSettingsWithServiceProcess();
178 }, 169 },
179 ); 170 );
180 171
181 reaction( 172 reaction(
182 () => ( 173 () => this.stores.settings.app.clipboardNotifications,
183 this.stores.settings.app.clipboardNotifications
184 ),
185 () => { 174 () => {
186 this._shareSettingsWithServiceProcess(); 175 this._shareSettingsWithServiceProcess();
187 }, 176 },
@@ -215,12 +204,12 @@ export default class ServicesStore extends Store {
215 * Run various maintenance tasks on services 204 * Run various maintenance tasks on services
216 */ 205 */
217 _serviceMaintenance() { 206 _serviceMaintenance() {
218 this.enabled.forEach(service => { 207 for (const service of this.enabled) {
219 // Defines which services should be hibernated or woken up 208 // Defines which services should be hibernated or woken up
220 if (!service.isActive) { 209 if (!service.isActive) {
221 if ( 210 if (
222 !service.lastHibernated && 211 !service.lastHibernated &&
223 (Date.now() - service.lastUsed) > 212 Date.now() - service.lastUsed >
224 ms(`${this.stores.settings.all.app.hibernationStrategy}s`) 213 ms(`${this.stores.settings.all.app.hibernationStrategy}s`)
225 ) { 214 ) {
226 // If service is stale, hibernate it. 215 // If service is stale, hibernate it.
@@ -230,8 +219,8 @@ export default class ServicesStore extends Store {
230 if ( 219 if (
231 service.lastHibernated && 220 service.lastHibernated &&
232 Number(this.stores.settings.all.app.wakeUpStrategy) > 0 && 221 Number(this.stores.settings.all.app.wakeUpStrategy) > 0 &&
233 (Date.now() - service.lastHibernated) > 222 Date.now() - service.lastHibernated >
234 ms(`${this.stores.settings.all.app.wakeUpStrategy}s`) 223 ms(`${this.stores.settings.all.app.wakeUpStrategy}s`)
235 ) { 224 ) {
236 // If service is in hibernation and the wakeup time has elapsed, wake it. 225 // If service is in hibernation and the wakeup time has elapsed, wake it.
237 this._awake({ serviceId: service.id }); 226 this._awake({ serviceId: service.id });
@@ -240,7 +229,7 @@ export default class ServicesStore extends Store {
240 229
241 if ( 230 if (
242 service.lastPoll && 231 service.lastPoll &&
243 (service.lastPoll - service.lastPollAnswer) > ms('1m') 232 service.lastPoll - service.lastPollAnswer > ms('1m')
244 ) { 233 ) {
245 // If service did not reply for more than 1m try to reload. 234 // If service did not reply for more than 1m try to reload.
246 if (!service.isActive) { 235 if (!service.isActive) {
@@ -261,7 +250,7 @@ export default class ServicesStore extends Store {
261 service.lostRecipeConnection = false; 250 service.lostRecipeConnection = false;
262 service.lostRecipeReloadAttempt = 0; 251 service.lostRecipeReloadAttempt = 0;
263 } 252 }
264 }); 253 }
265 } 254 }
266 255
267 // Computed props 256 // Computed props
@@ -270,8 +259,7 @@ export default class ServicesStore extends Store {
270 const services = this.allServicesRequest.execute().result; 259 const services = this.allServicesRequest.execute().result;
271 if (services) { 260 if (services) {
272 return observable( 261 return observable(
273 services 262 [...services]
274 .slice()
275 .slice() 263 .slice()
276 .sort((a, b) => a.order - b.order) 264 .sort((a, b) => a.order - b.order)
277 .map((s, index) => { 265 .map((s, index) => {
@@ -318,11 +306,11 @@ export default class ServicesStore extends Store {
318 // Check if workspace needs to be kept loaded 306 // Check if workspace needs to be kept loaded
319 if (workspace.services.includes(KEEP_WS_LOADED_USID)) { 307 if (workspace.services.includes(KEEP_WS_LOADED_USID)) {
320 // Get services for workspace 308 // Get services for workspace
321 const serviceIDs = workspace.services.filter( 309 const serviceIDs = new Set(
322 i => i !== KEEP_WS_LOADED_USID, 310 workspace.services.filter(i => i !== KEEP_WS_LOADED_USID),
323 ); 311 );
324 const wsServices = filteredServices.filter(service => 312 const wsServices = filteredServices.filter(service =>
325 serviceIDs.includes(service.id), 313 serviceIDs.has(service.id),
326 ); 314 );
327 315
328 displayedServices = [...displayedServices, ...wsServices]; 316 displayedServices = [...displayedServices, ...wsServices];
@@ -410,12 +398,14 @@ export default class ServicesStore extends Store {
410 customIcon: false, 398 customIcon: false,
411 isDarkModeEnabled: false, 399 isDarkModeEnabled: false,
412 spellcheckerLanguage: 400 spellcheckerLanguage:
413 SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage], 401 SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage],
414 userAgentPref: '', 402 userAgentPref: '',
415 ...serviceData, 403 ...serviceData,
416 }; 404 };
417 405
418 const data = skipCleanup ? serviceData : this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 406 const data = skipCleanup
407 ? serviceData
408 : this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
419 409
420 const response = await this.createServiceRequest.execute(recipeId, data) 410 const response = await this.createServiceRequest.execute(recipeId, data)
421 ._promise; 411 ._promise;
@@ -562,7 +552,8 @@ export default class ServicesStore extends Store {
562 // Write your scripts here 552 // Write your scripts here
563 console.log("Hello, World!", config); 553 console.log("Hello, World!", config);
564}; 554};
565`); 555`,
556 );
566 } 557 }
567 } else { 558 } else {
568 ensureFileSync(filePath); 559 ensureFileSync(filePath);
@@ -580,9 +571,9 @@ export default class ServicesStore extends Store {
580 if (!keepActiveRoute) this.stores.router.push('/'); 571 if (!keepActiveRoute) this.stores.router.push('/');
581 const service = this.one(serviceId); 572 const service = this.one(serviceId);
582 573
583 this.all.forEach(s => { 574 for (const s of this.all) {
584 s.isActive = false; 575 s.isActive = false;
585 }); 576 }
586 service.isActive = true; 577 service.isActive = true;
587 this._awake({ serviceId: service.id }); 578 this._awake({ serviceId: service.id });
588 579
@@ -618,9 +609,9 @@ export default class ServicesStore extends Store {
618 this.allDisplayed.length, 609 this.allDisplayed.length,
619 ); 610 );
620 611
621 this.all.forEach(s => { 612 for (const s of this.all) {
622 s.isActive = false; 613 s.isActive = false;
623 }); 614 }
624 this.allDisplayed[nextIndex].isActive = true; 615 this.allDisplayed[nextIndex].isActive = true;
625 } 616 }
626 617
@@ -631,9 +622,9 @@ export default class ServicesStore extends Store {
631 this.allDisplayed.length, 622 this.allDisplayed.length,
632 ); 623 );
633 624
634 this.all.forEach(s => { 625 for (const s of this.all) {
635 s.isActive = false; 626 s.isActive = false;
636 }); 627 }
637 this.allDisplayed[prevIndex].isActive = true; 628 this.allDisplayed[prevIndex].isActive = true;
638 } 629 }
639 630
@@ -699,101 +690,128 @@ export default class ServicesStore extends Store {
699 @action _handleIPCMessage({ serviceId, channel, args }) { 690 @action _handleIPCMessage({ serviceId, channel, args }) {
700 const service = this.one(serviceId); 691 const service = this.one(serviceId);
701 692
702 if (channel === 'hello') { 693 // eslint-disable-next-line default-case
703 debug('Received hello event from', serviceId); 694 switch (channel) {
704 695 case 'hello': {
705 this._initRecipePolling(service.id); 696 debug('Received hello event from', serviceId);
706 this._initializeServiceRecipeInWebview(serviceId);
707 this._shareSettingsWithServiceProcess();
708 } else if (channel === 'alive') {
709 service.lastPollAnswer = Date.now();
710 } else if (channel === 'message-counts') {
711 debug(`Received unread message info from '${serviceId}'`, args[0]);
712 697
713 this.actions.service.setUnreadMessageCount({ 698 this._initRecipePolling(service.id);
714 serviceId, 699 this._initializeServiceRecipeInWebview(serviceId);
715 count: { 700 this._shareSettingsWithServiceProcess();
716 direct: args[0].direct,
717 indirect: args[0].indirect,
718 },
719 });
720 } else if (channel === 'notification') {
721 const { options } = args[0];
722 701
723 // Check if we are in scheduled Do-not-Disturb time 702 break;
724 const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } = 703 }
725 this.stores.settings.all.app; 704 case 'alive': {
705 service.lastPollAnswer = Date.now();
726 706
727 if ( 707 break;
728 scheduledDNDEnabled &&
729 isInTimeframe(scheduledDNDStart, scheduledDNDEnd)
730 ) {
731 return;
732 } 708 }
709 case 'message-counts': {
710 debug(`Received unread message info from '${serviceId}'`, args[0]);
733 711
734 if ( 712 this.actions.service.setUnreadMessageCount({
735 service.recipe.hasNotificationSound || 713 serviceId,
736 service.isMuted || 714 count: {
737 this.stores.settings.all.app.isAppMuted 715 direct: args[0].direct,
738 ) { 716 indirect: args[0].indirect,
739 Object.assign(options, { 717 },
740 silent: true,
741 }); 718 });
719
720 break;
742 } 721 }
722 case 'notification': {
723 const { options } = args[0];
743 724
744 if (service.isNotificationEnabled) { 725 // Check if we are in scheduled Do-not-Disturb time
745 let title = `Notification from ${service.name}`; 726 const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } =
746 if (!this.stores.settings.all.app.privateNotifications) { 727 this.stores.settings.all.app;
747 options.body = typeof options.body === 'string' ? options.body : ''; 728
748 title = 729 if (
749 typeof args[0].title === 'string' ? args[0].title : service.name; 730 scheduledDNDEnabled &&
750 } else { 731 isInTimeframe(scheduledDNDStart, scheduledDNDEnd)
751 // Remove message data from notification in private mode 732 ) {
752 options.body = ''; 733 return;
753 options.icon = '/assets/img/notification-badge.gif';
754 } 734 }
755 735
756 console.log(title, options); 736 if (
737 service.recipe.hasNotificationSound ||
738 service.isMuted ||
739 this.stores.settings.all.app.isAppMuted
740 ) {
741 Object.assign(options, {
742 silent: true,
743 });
744 }
757 745
758 this.actions.app.notify({ 746 if (service.isNotificationEnabled) {
759 notificationId: args[0].notificationId, 747 let title = `Notification from ${service.name}`;
760 title, 748 if (!this.stores.settings.all.app.privateNotifications) {
761 options, 749 options.body = typeof options.body === 'string' ? options.body : '';
762 serviceId, 750 title =
763 }); 751 typeof args[0].title === 'string' ? args[0].title : service.name;
752 } else {
753 // Remove message data from notification in private mode
754 options.body = '';
755 options.icon = '/assets/img/notification-badge.gif';
756 }
757
758 console.log(title, options);
759
760 this.actions.app.notify({
761 notificationId: args[0].notificationId,
762 title,
763 options,
764 serviceId,
765 });
766 }
767
768 break;
764 } 769 }
765 } else if (channel === 'avatar') { 770 case 'avatar': {
766 const url = args[0]; 771 const url = args[0];
767 if (service.iconUrl !== url && !service.hasCustomUploadedIcon) { 772 if (service.iconUrl !== url && !service.hasCustomUploadedIcon) {
768 service.customIconUrl = url; 773 service.customIconUrl = url;
774
775 this.actions.service.updateService({
776 serviceId,
777 serviceData: {
778 customIconUrl: url,
779 },
780 redirect: false,
781 });
782 }
769 783
770 this.actions.service.updateService({ 784 break;
771 serviceId,
772 serviceData: {
773 customIconUrl: url,
774 },
775 redirect: false,
776 });
777 } 785 }
778 } else if (channel === 'new-window') { 786 case 'new-window': {
779 const url = args[0]; 787 const url = args[0];
780 788
781 this.actions.app.openExternalUrl({ url }); 789 this.actions.app.openExternalUrl({ url });
782 } else if (channel === 'set-service-spellchecker-language') { 790
783 if (!args) { 791 break;
784 console.warn('Did not receive locale'); 792 }
785 } else { 793 case 'set-service-spellchecker-language': {
786 this.actions.service.updateService({ 794 if (!args) {
787 serviceId, 795 console.warn('Did not receive locale');
788 serviceData: { 796 } else {
789 spellcheckerLanguage: args[0] === 'reset' ? '' : args[0], 797 this.actions.service.updateService({
790 }, 798 serviceId,
791 redirect: false, 799 serviceData: {
792 }); 800 spellcheckerLanguage: args[0] === 'reset' ? '' : args[0],
801 },
802 redirect: false,
803 });
804 }
805
806 break;
807 }
808 case 'feature:todos': {
809 Object.assign(args[0].data, { serviceId });
810 this.actions.todos.handleHostMessage(args[0]);
811
812 break;
793 } 813 }
794 } else if (channel === 'feature:todos') { 814 // No default
795 Object.assign(args[0].data, { serviceId });
796 this.actions.todos.handleHostMessage(args[0]);
797 } 815 }
798 } 816 }
799 817
@@ -809,13 +827,13 @@ export default class ServicesStore extends Store {
809 } 827 }
810 828
811 @action _sendIPCMessageToAllServices({ channel, args }) { 829 @action _sendIPCMessageToAllServices({ channel, args }) {
812 this.all.forEach(s => 830 for (const s of this.all) {
813 this.actions.service.sendIPCMessage({ 831 this.actions.service.sendIPCMessage({
814 serviceId: s.id, 832 serviceId: s.id,
815 channel, 833 channel,
816 args, 834 args,
817 }), 835 });
818 ); 836 }
819 } 837 }
820 838
821 @action _openWindow({ event }) { 839 @action _openWindow({ event }) {
@@ -863,11 +881,11 @@ export default class ServicesStore extends Store {
863 } 881 }
864 882
865 @action _reloadAll() { 883 @action _reloadAll() {
866 this.enabled.forEach(s => 884 for (const s of this.enabled) {
867 this._reload({ 885 this._reload({
868 serviceId: s.id, 886 serviceId: s.id,
869 }), 887 });
870 ); 888 }
871 } 889 }
872 890
873 @action _reloadUpdatedServices() { 891 @action _reloadUpdatedServices() {
@@ -901,17 +919,17 @@ export default class ServicesStore extends Store {
901 919
902 const services = {}; 920 const services = {};
903 // TODO: simplify this 921 // TODO: simplify this
904 this.all.forEach((s, index) => { 922 for (const [index] of this.all.entries()) {
905 services[this.all[index].id] = index; 923 services[this.all[index].id] = index;
906 }); 924 }
907 925
908 this.reorderServicesRequest.execute(services); 926 this.reorderServicesRequest.execute(services);
909 this.allServicesRequest.patch(data => { 927 this.allServicesRequest.patch(data => {
910 data.forEach(s => { 928 for (const s of data) {
911 const service = s; 929 const service = s;
912 930
913 service.order = services[s.id]; 931 service.order = services[s.id];
914 }); 932 }
915 }); 933 });
916 } 934 }
917 935
@@ -1001,13 +1019,14 @@ export default class ServicesStore extends Store {
1001 }`, 1019 }`,
1002 ); 1020 );
1003 1021
1022 // eslint-disable-next-line unicorn/consistent-function-scoping
1004 const resetTimer = service => { 1023 const resetTimer = service => {
1005 service.lastPollAnswer = Date.now(); 1024 service.lastPollAnswer = Date.now();
1006 service.lastPoll = Date.now(); 1025 service.lastPoll = Date.now();
1007 }; 1026 };
1008 1027
1009 if (!serviceId) { 1028 if (!serviceId) {
1010 this.allDisplayed.forEach(service => resetTimer(service)); 1029 for (const service of this.allDisplayed) resetTimer(service);
1011 } else { 1030 } else {
1012 const service = this.one(serviceId); 1031 const service = this.one(serviceId);
1013 if (service) { 1032 if (service) {
@@ -1043,7 +1062,7 @@ export default class ServicesStore extends Store {
1043 1062
1044 _mapActiveServiceToServiceModelReaction() { 1063 _mapActiveServiceToServiceModelReaction() {
1045 const { activeService } = this.stores.settings.all.service; 1064 const { activeService } = this.stores.settings.all.service;
1046 if (this.allDisplayed.length) { 1065 if (this.allDisplayed.length > 0) {
1047 this.allDisplayed.map(service => 1066 this.allDisplayed.map(service =>
1048 Object.assign(service, { 1067 Object.assign(service, {
1049 isActive: activeService 1068 isActive: activeService
@@ -1102,14 +1121,14 @@ export default class ServicesStore extends Store {
1102 const { enabled } = this; 1121 const { enabled } = this;
1103 const { isAppMuted } = this.stores.settings.app; 1122 const { isAppMuted } = this.stores.settings.app;
1104 1123
1105 enabled.forEach(service => { 1124 for (const service of enabled) {
1106 const { isAttached } = service; 1125 const { isAttached } = service;
1107 const isMuted = isAppMuted || service.isMuted; 1126 const isMuted = isAppMuted || service.isMuted;
1108 1127
1109 if (isAttached) { 1128 if (isAttached) {
1110 service.webview.audioMuted = isMuted; 1129 service.webview.audioMuted = isMuted;
1111 } 1130 }
1112 }); 1131 }
1113 } 1132 }
1114 1133
1115 _shareSettingsWithServiceProcess() { 1134 _shareSettingsWithServiceProcess() {
@@ -1151,7 +1170,7 @@ export default class ServicesStore extends Store {
1151 1170
1152 if ( 1171 if (
1153 this.allDisplayed.findIndex(service => service.isActive) === -1 && 1172 this.allDisplayed.findIndex(service => service.isActive) === -1 &&
1154 this.allDisplayed.length !== 0 1173 this.allDisplayed.length > 0
1155 ) { 1174 ) {
1156 debug('No active service found, setting active service to index 0'); 1175 debug('No active service found, setting active service to index 0');
1157 1176
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index 9aade974c..690a18374 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -1,11 +1,11 @@
1import { ipcRenderer } from 'electron'; 1import { ipcRenderer } from 'electron';
2import { getCurrentWindow } from '@electron/remote'; 2import { getCurrentWindow } from '@electron/remote';
3import { 3import { action, computed, observable, reaction } from 'mobx';
4 action, computed, observable, reaction,
5} from 'mobx';
6import localStorage from 'mobx-localstorage'; 4import localStorage from 'mobx-localstorage';
7import { 5import {
8 FILE_SYSTEM_SETTINGS_TYPES, LOCAL_SERVER, SEARCH_ENGINE_DDG, 6 FILE_SYSTEM_SETTINGS_TYPES,
7 LOCAL_SERVER,
8 SEARCH_ENGINE_DDG,
9} from '../config'; 9} from '../config';
10import { API, DEFAULT_APP_SETTINGS } from '../environment'; 10import { API, DEFAULT_APP_SETTINGS } from '../environment';
11import { getLocale } from '../helpers/i18n-helpers'; 11import { getLocale } from '../helpers/i18n-helpers';
@@ -17,7 +17,10 @@ import Store from './lib/Store';
17const debug = require('debug')('Ferdi:SettingsStore'); 17const debug = require('debug')('Ferdi:SettingsStore');
18 18
19export default class SettingsStore extends Store { 19export default class SettingsStore extends Store {
20 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings'); 20 @observable updateAppSettingsRequest = new Request(
21 this.api.local,
22 'updateAppSettings',
23 );
21 24
22 startup = true; 25 startup = true;
23 26
@@ -40,9 +43,7 @@ export default class SettingsStore extends Store {
40 await this._migrate(); 43 await this._migrate();
41 44
42 reaction( 45 reaction(
43 () => ( 46 () => this.all.app.autohideMenuBar,
44 this.all.app.autohideMenuBar
45 ),
46 () => { 47 () => {
47 const currentWindow = getCurrentWindow(); 48 const currentWindow = getCurrentWindow();
48 currentWindow.setMenuBarVisibility(!this.all.app.autohideMenuBar); 49 currentWindow.setMenuBarVisibility(!this.all.app.autohideMenuBar);
@@ -51,10 +52,8 @@ export default class SettingsStore extends Store {
51 ); 52 );
52 53
53 reaction( 54 reaction(
54 () => ( 55 () => this.all.app.server,
55 this.all.app.server 56 server => {
56 ),
57 (server) => {
58 if (server === LOCAL_SERVER) { 57 if (server === LOCAL_SERVER) {
59 ipcRenderer.send('startLocalServer'); 58 ipcRenderer.send('startLocalServer');
60 } 59 }
@@ -65,7 +64,10 @@ export default class SettingsStore extends Store {
65 // Inactivity lock timer 64 // Inactivity lock timer
66 let inactivityTimer; 65 let inactivityTimer;
67 getCurrentWindow().on('blur', () => { 66 getCurrentWindow().on('blur', () => {
68 if (this.all.app.lockingFeatureEnabled && this.all.app.inactivityLock !== 0) { 67 if (
68 this.all.app.lockingFeatureEnabled &&
69 this.all.app.inactivityLock !== 0
70 ) {
69 inactivityTimer = setTimeout(() => { 71 inactivityTimer = setTimeout(() => {
70 this.actions.settings.update({ 72 this.actions.settings.update({
71 type: 'app', 73 type: 'app',
@@ -84,7 +86,11 @@ export default class SettingsStore extends Store {
84 86
85 ipcRenderer.on('appSettings', (event, resp) => { 87 ipcRenderer.on('appSettings', (event, resp) => {
86 // Lock on startup if enabled in settings 88 // Lock on startup if enabled in settings
87 if (this.startup && resp.type === 'app' && resp.data.lockingFeatureEnabled) { 89 if (
90 this.startup &&
91 resp.type === 'app' &&
92 resp.data.lockingFeatureEnabled
93 ) {
88 this.startup = false; 94 this.startup = false;
89 process.nextTick(() => { 95 process.nextTick(() => {
90 if (!this.all.app.locked) { 96 if (!this.all.app.locked) {
@@ -97,9 +103,9 @@ export default class SettingsStore extends Store {
97 ipcRenderer.send('initialAppSettings', resp); 103 ipcRenderer.send('initialAppSettings', resp);
98 }); 104 });
99 105
100 this.fileSystemSettingsTypes.forEach((type) => { 106 for (const type of this.fileSystemSettingsTypes) {
101 ipcRenderer.send('getAppSettings', type); 107 ipcRenderer.send('getAppSettings', type);
102 }); 108 }
103 } 109 }
104 110
105 @computed get app() { 111 @computed get app() {
@@ -111,15 +117,19 @@ export default class SettingsStore extends Store {
111 } 117 }
112 118
113 @computed get service() { 119 @computed get service() {
114 return localStorage.getItem('service') || { 120 return (
115 activeService: '', 121 localStorage.getItem('service') || {
116 }; 122 activeService: '',
123 }
124 );
117 } 125 }
118 126
119 @computed get stats() { 127 @computed get stats() {
120 return localStorage.getItem('stats') || { 128 return (
121 activeService: '', 129 localStorage.getItem('stats') || {
122 }; 130 activeService: '',
131 }
132 );
123 } 133 }
124 134
125 @computed get migration() { 135 @computed get migration() {
@@ -230,9 +240,7 @@ export default class SettingsStore extends Store {
230 }); 240 });
231 241
232 this._ensureMigrationAndMarkDone('5.4.4-beta.2-settings', () => { 242 this._ensureMigrationAndMarkDone('5.4.4-beta.2-settings', () => {
233 const { 243 const { showServiceNavigationBar } = this.all.app;
234 showServiceNavigationBar,
235 } = this.all.app;
236 244
237 this.actions.settings.update({ 245 this.actions.settings.update({
238 type: 'app', 246 type: 'app',
@@ -248,7 +256,7 @@ export default class SettingsStore extends Store {
248 data: { 256 data: {
249 todoServer: 'isUsingCustomTodoService', 257 todoServer: 'isUsingCustomTodoService',
250 customTodoServer: legacySettings.todoServer, 258 customTodoServer: legacySettings.todoServer,
251 automaticUpdates: !(legacySettings.noUpdates), 259 automaticUpdates: !legacySettings.noUpdates,
252 }, 260 },
253 }); 261 });
254 262
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 2e009893a..e3d57c662 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -46,7 +46,10 @@ export default class UserStore extends Store {
46 46
47 @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); 47 @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo');
48 48
49 @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices'); 49 @observable getLegacyServicesRequest = new CachedRequest(
50 this.api.user,
51 'getLegacyServices',
52 );
50 53
51 @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete'); 54 @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete');
52 55
@@ -81,13 +84,17 @@ export default class UserStore extends Store {
81 84
82 // Register action handlers 85 // Register action handlers
83 this.actions.user.login.listen(this._login.bind(this)); 86 this.actions.user.login.listen(this._login.bind(this));
84 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this)); 87 this.actions.user.retrievePassword.listen(
88 this._retrievePassword.bind(this),
89 );
85 this.actions.user.logout.listen(this._logout.bind(this)); 90 this.actions.user.logout.listen(this._logout.bind(this));
86 this.actions.user.signup.listen(this._signup.bind(this)); 91 this.actions.user.signup.listen(this._signup.bind(this));
87 this.actions.user.invite.listen(this._invite.bind(this)); 92 this.actions.user.invite.listen(this._invite.bind(this));
88 this.actions.user.update.listen(this._update.bind(this)); 93 this.actions.user.update.listen(this._update.bind(this));
89 this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); 94 this.actions.user.resetStatus.listen(this._resetStatus.bind(this));
90 this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this)); 95 this.actions.user.importLegacyServices.listen(
96 this._importLegacyServices.bind(this),
97 );
91 this.actions.user.delete.listen(this._delete.bind(this)); 98 this.actions.user.delete.listen(this._delete.bind(this));
92 99
93 // Reactions 100 // Reactions
@@ -176,7 +183,14 @@ export default class UserStore extends Store {
176 } 183 }
177 184
178 @action async _signup({ 185 @action async _signup({
179 firstname, lastname, email, password, accountType, company, plan, currency, 186 firstname,
187 lastname,
188 email,
189 password,
190 accountType,
191 company,
192 plan,
193 currency,
180 }) { 194 }) {
181 const authToken = await this.signupRequest.execute({ 195 const authToken = await this.signupRequest.execute({
182 firstname, 196 firstname,
@@ -205,7 +219,7 @@ export default class UserStore extends Store {
205 } 219 }
206 220
207 @action async _invite({ invites }) { 221 @action async _invite({ invites }) {
208 const data = invites.filter((invite) => invite.email !== ''); 222 const data = invites.filter(invite => invite.email !== '');
209 223
210 const response = await this.inviteRequest.execute(data)._promise; 224 const response = await this.inviteRequest.execute(data)._promise;
211 225
@@ -220,7 +234,8 @@ export default class UserStore extends Store {
220 @action async _update({ userData }) { 234 @action async _update({ userData }) {
221 if (!this.isLoggedIn) return; 235 if (!this.isLoggedIn) return;
222 236
223 const response = await this.updateUserInfoRequest.execute(userData)._promise; 237 const response = await this.updateUserInfoRequest.execute(userData)
238 ._promise;
224 239
225 this.getUserInfoRequest.patch(() => response.data); 240 this.getUserInfoRequest.patch(() => response.data);
226 this.actionStatus = response.status || []; 241 this.actionStatus = response.status || [];
@@ -250,19 +265,27 @@ export default class UserStore extends Store {
250 this.isImportLegacyServicesExecuting = true; 265 this.isImportLegacyServicesExecuting = true;
251 266
252 // Reduces recipe duplicates 267 // Reduces recipe duplicates
253 const recipes = services.filter((obj, pos, arr) => arr.map((mapObj) => mapObj.recipe.id).indexOf(obj.recipe.id) === pos).map((s) => s.recipe.id); 268 const recipes = services
269 .filter(
270 (obj, pos, arr) =>
271 arr.map(mapObj => mapObj.recipe.id).indexOf(obj.recipe.id) === pos,
272 )
273 .map(s => s.recipe.id);
254 274
255 // Install recipes 275 // Install recipes
256 for (const recipe of recipes) { // eslint-disable-line no-unused-vars 276 for (const recipe of recipes) {
257 // eslint-disable-next-line 277 // eslint-disable-line no-unused-vars
278 // eslint-disable-next-line no-await-in-loop
258 await this.stores.recipes._install({ recipeId: recipe }); 279 await this.stores.recipes._install({ recipeId: recipe });
259 } 280 }
260 281
261 for (const service of services) { // eslint-disable-line no-unused-vars 282 for (const service of services) {
283 // eslint-disable-line no-unused-vars
262 this.actions.service.createFromLegacyService({ 284 this.actions.service.createFromLegacyService({
263 data: service, 285 data: service,
264 }); 286 });
265 await this.stores.services.createServiceRequest._promise; // eslint-disable-line 287 // eslint-disable-next-line no-await-in-loop
288 await this.stores.services.createServiceRequest._promise;
266 } 289 }
267 290
268 this.isImportLegacyServicesExecuting = false; 291 this.isImportLegacyServicesExecuting = false;
@@ -281,8 +304,7 @@ export default class UserStore extends Store {
281 304
282 const { router } = this.stores; 305 const { router } = this.stores;
283 const currentRoute = window.location.hash; 306 const currentRoute = window.location.hash;
284 if (!this.isLoggedIn 307 if (!this.isLoggedIn && currentRoute.includes('token=')) {
285 && currentRoute.includes('token=')) {
286 router.push(this.WELCOME_ROUTE); 308 router.push(this.WELCOME_ROUTE);
287 const token = currentRoute.split('=')[1]; 309 const token = currentRoute.split('=')[1];
288 310
@@ -293,20 +315,18 @@ export default class UserStore extends Store {
293 this._tokenLogin(token); 315 this._tokenLogin(token);
294 }, 1000); 316 }, 1000);
295 } 317 }
296 } else if (!this.isLoggedIn 318 } else if (!this.isLoggedIn && !currentRoute.includes(this.BASE_ROUTE)) {
297 && !currentRoute.includes(this.BASE_ROUTE)) {
298 router.push(this.WELCOME_ROUTE); 319 router.push(this.WELCOME_ROUTE);
299 } else if (this.isLoggedIn 320 } else if (this.isLoggedIn && currentRoute === this.LOGOUT_ROUTE) {
300 && currentRoute === this.LOGOUT_ROUTE) {
301 this.actions.user.logout(); 321 this.actions.user.logout();
302 router.push(this.LOGIN_ROUTE); 322 router.push(this.LOGIN_ROUTE);
303 } else if (this.isLoggedIn 323 } else if (
304 && currentRoute.includes(this.BASE_ROUTE) 324 this.isLoggedIn &&
305 && (this.hasCompletedSignup 325 currentRoute.includes(this.BASE_ROUTE) &&
306 || this.hasCompletedSignup === null)) { 326 (this.hasCompletedSignup || this.hasCompletedSignup === null) &&
307 if (!isDevMode) { 327 !isDevMode
308 this.stores.router.push('/'); 328 ) {
309 } 329 this.stores.router.push('/');
310 } 330 }
311 }; 331 };
312 332
@@ -316,7 +336,7 @@ export default class UserStore extends Store {
316 let data; 336 let data;
317 try { 337 try {
318 data = await this.getUserInfoRequest.execute()._promise; 338 data = await this.getUserInfoRequest.execute()._promise;
319 } catch (e) { 339 } catch {
320 return false; 340 return false;
321 } 341 }
322 342
@@ -336,12 +356,12 @@ export default class UserStore extends Store {
336 try { 356 try {
337 const decoded = jwt.decode(authToken); 357 const decoded = jwt.decode(authToken);
338 358
339 return ({ 359 return {
340 id: decoded.userId, 360 id: decoded.userId,
341 tokenExpiry: moment.unix(decoded.exp).toISOString(), 361 tokenExpiry: moment.unix(decoded.exp).toISOString(),
342 authToken, 362 authToken,
343 }); 363 };
344 } catch (err) { 364 } catch {
345 this._logout(); 365 this._logout();
346 return false; 366 return false;
347 } 367 }
@@ -372,7 +392,7 @@ export default class UserStore extends Store {
372 async _migrateUserLocale() { 392 async _migrateUserLocale() {
373 try { 393 try {
374 await this.getUserInfoRequest._promise; 394 await this.getUserInfoRequest._promise;
375 } catch (e) { 395 } catch {
376 return false; 396 return false;
377 } 397 }
378 398
diff --git a/src/stores/index.ts b/src/stores/index.ts
index 4cd4e92ea..1760ddfa2 100644
--- a/src/stores/index.ts
+++ b/src/stores/index.ts
@@ -34,10 +34,10 @@ export default (api, actions, router) => {
34 }); 34 });
35 35
36 // Initialize all stores 36 // Initialize all stores
37 Object.keys(stores).forEach(name => { 37 for (const name of Object.keys(stores)) {
38 if (stores[name] && stores[name].initialize) { 38 if (stores[name] && stores[name].initialize) {
39 stores[name].initialize(); 39 stores[name].initialize();
40 } 40 }
41 }); 41 }
42 return stores; 42 return stores;
43}; 43};
diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.js
index 94f615144..a6dd47f7d 100644
--- a/src/stores/lib/CachedRequest.js
+++ b/src/stores/lib/CachedRequest.js
@@ -1,4 +1,3 @@
1// @flow
2import { action } from 'mobx'; 1import { action } from 'mobx';
3import { isEqual, remove } from 'lodash'; 2import { isEqual, remove } from 'lodash';
4import Request from './Request'; 3import Request from './Request';
@@ -30,48 +29,60 @@ export default class CachedRequest extends Request {
30 29
31 // This timeout is necessary to avoid warnings from mobx 30 // This timeout is necessary to avoid warnings from mobx
32 // regarding triggering actions as side-effect of getters 31 // regarding triggering actions as side-effect of getters
33 setTimeout(action(() => { 32 setTimeout(
34 this.isExecuting = true; 33 action(() => {
35 // Apply the previous result from this call immediately (cached) 34 this.isExecuting = true;
36 if (existingApiCall) { 35 // Apply the previous result from this call immediately (cached)
37 this.result = existingApiCall.result; 36 if (existingApiCall) {
38 } 37 this.result = existingApiCall.result;
39 }), 0); 38 }
39 }),
40 0,
41 );
40 42
41 // Issue api call & save it as promise that is handled to update the results of the operation 43 // Issue api call & save it as promise that is handled to update the results of the operation
42 this._promise = new Promise((resolve) => { 44 this._promise = new Promise(resolve => {
43 this._api[this._method](...callArgs) 45 this._api[this._method](...callArgs)
44 .then((result) => { 46 .then(result => {
45 setTimeout(action(() => { 47 setTimeout(
46 this.result = result; 48 action(() => {
47 if (this._currentApiCall) this._currentApiCall.result = result; 49 this.result = result;
48 this.isExecuting = false; 50 if (this._currentApiCall) this._currentApiCall.result = result;
49 this.isError = false; 51 this.isExecuting = false;
50 this.wasExecuted = true; 52 this.isError = false;
51 this._isInvalidated = false; 53 this.wasExecuted = true;
52 this._isWaitingForResponse = false; 54 this._isInvalidated = false;
53 this._triggerHooks(); 55 this._isWaitingForResponse = false;
54 resolve(result); 56 this._triggerHooks();
55 }), 1); 57 resolve(result);
58 }),
59 1,
60 );
56 return result; 61 return result;
57 }) 62 })
58 .catch(action((error) => { 63 .catch(
59 setTimeout(action(() => { 64 action(error => {
60 this.error = error; 65 setTimeout(
61 this.isExecuting = false; 66 action(() => {
62 this.isError = true; 67 this.error = error;
63 this.wasExecuted = true; 68 this.isExecuting = false;
64 this._isWaitingForResponse = false; 69 this.isError = true;
65 this._triggerHooks(); 70 this.wasExecuted = true;
66 // reject(error); 71 this._isWaitingForResponse = false;
67 }), 1); 72 this._triggerHooks();
68 })); 73 // reject(error);
74 }),
75 1,
76 );
77 }),
78 );
69 }); 79 });
70 80
71 this._isWaitingForResponse = true; 81 this._isWaitingForResponse = true;
72 return this; 82 return this;
73 } 83 }
74 84
85 // eslint-disable-next-line unicorn/no-object-as-default-parameter
75 invalidate(options = { immediately: false }) { 86 invalidate(options = { immediately: false }) {
76 this._isInvalidated = true; 87 this._isInvalidated = true;
77 if (options.immediately && this._currentApiCall) { 88 if (options.immediately && this._currentApiCall) {
@@ -81,18 +92,21 @@ export default class CachedRequest extends Request {
81 } 92 }
82 93
83 patch(modify) { 94 patch(modify) {
84 return new Promise((resolve) => { 95 return new Promise(resolve => {
85 setTimeout(action(() => { 96 setTimeout(
86 const override = modify(this.result); 97 action(() => {
87 if (override !== undefined) this.result = override; 98 const override = modify(this.result);
88 if (this._currentApiCall) this._currentApiCall.result = this.result; 99 if (override !== undefined) this.result = override;
89 resolve(this); 100 if (this._currentApiCall) this._currentApiCall.result = this.result;
90 }), 0); 101 resolve(this);
102 }),
103 0,
104 );
91 }); 105 });
92 } 106 }
93 107
94 removeCacheForCallWith(...args) { 108 removeCacheForCallWith(...args) {
95 remove(this._apiCalls, (c) => isEqual(c.args, args)); 109 remove(this._apiCalls, c => isEqual(c.args, args));
96 } 110 }
97 111
98 _addApiCall(args) { 112 _addApiCall(args) {
@@ -102,6 +116,6 @@ export default class CachedRequest extends Request {
102 } 116 }
103 117
104 _findApiCall(args) { 118 _findApiCall(args) {
105 return this._apiCalls.find((c) => isEqual(c.args, args)); 119 return this._apiCalls.find(c => isEqual(c.args, args));
106 } 120 }
107} 121}
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js
index 32ffe4367..39f32729a 100644
--- a/src/stores/lib/Request.js
+++ b/src/stores/lib/Request.js
@@ -107,7 +107,7 @@ export default class Request {
107 } 107 }
108 108
109 _triggerHooks() { 109 _triggerHooks() {
110 Request._hooks.forEach((hook) => hook(this)); 110 for (const hook of Request._hooks) hook(this);
111 } 111 }
112 112
113 reset = () => { 113 reset = () => {
diff --git a/src/stores/lib/Store.js b/src/stores/lib/Store.js
index b03a7e725..b39070ce8 100644
--- a/src/stores/lib/Store.js
+++ b/src/stores/lib/Store.js
@@ -28,18 +28,18 @@ export default class Store {
28 } 28 }
29 29
30 registerReactions(reactions) { 30 registerReactions(reactions) {
31 reactions.forEach((reaction) => this._reactions.push(new Reaction(reaction))); 31 for (const reaction of reactions) this._reactions.push(new Reaction(reaction));
32 } 32 }
33 33
34 setup() {} 34 setup() {}
35 35
36 initialize() { 36 initialize() {
37 this.setup(); 37 this.setup();
38 this._reactions.forEach((reaction) => reaction.start()); 38 for (const reaction of this._reactions) reaction.start();
39 } 39 }
40 40
41 teardown() { 41 teardown() {
42 this._reactions.forEach((reaction) => reaction.stop()); 42 for (const reaction of this._reactions) reaction.stop();
43 } 43 }
44 44
45 resetStatus() { 45 resetStatus() {