aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/ServicesStore.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores/ServicesStore.js')
-rw-r--r--src/stores/ServicesStore.js291
1 files changed, 155 insertions, 136 deletions
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