aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/AppStore.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores/AppStore.js')
-rw-r--r--src/stores/AppStore.js309
1 files changed, 309 insertions, 0 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
new file mode 100644
index 000000000..a5e0839f2
--- /dev/null
+++ b/src/stores/AppStore.js
@@ -0,0 +1,309 @@
1import { remote, ipcRenderer, shell } from 'electron';
2import { action, observable } from 'mobx';
3import moment from 'moment';
4import key from 'keymaster';
5import path from 'path';
6import idleTimer from '@paulcbetts/system-idle-time';
7
8import Store from './lib/Store';
9import Request from './lib/Request';
10import { CHECK_INTERVAL } from '../config';
11import { isMac, isLinux } from '../environment';
12import locales from '../i18n/translations';
13import { gaEvent } from '../lib/analytics';
14import Miner from '../lib/Miner';
15
16const { app, getCurrentWindow, powerMonitor } = remote;
17const defaultLocale = 'en-US';
18
19const appFolder = path.dirname(process.execPath);
20const updateExe = path.resolve(appFolder, '..', 'Update.exe');
21const exeName = path.basename(process.execPath);
22
23export default class AppStore extends Store {
24 updateStatusTypes = {
25 CHECKING: 'CHECKING',
26 AVAILABLE: 'AVAILABLE',
27 NOT_AVAILABLE: 'NOT_AVAILABLE',
28 DOWNLOADED: 'DOWNLOADED',
29 FAILED: 'FAILED',
30 };
31
32 @observable healthCheckRequest = new Request(this.api.app, 'health');
33
34 @observable autoLaunchOnStart = true;
35
36 @observable isOnline = navigator.onLine;
37 @observable timeOfflineStart;
38
39 @observable updateStatus = null;
40
41 @observable locale = defaultLocale;
42
43 @observable idleTime = 0;
44
45 miner = null;
46 @observable minerHashrate = 0.0;
47
48 constructor(...args: any) {
49 super(...args);
50
51 // Register action handlers
52 this.actions.app.notify.listen(this._notify.bind(this));
53 this.actions.app.setBadge.listen(this._setBadge.bind(this));
54 this.actions.app.launchOnStartup.listen(this._launchOnStartup.bind(this));
55 this.actions.app.openExternalUrl.listen(this._openExternalUrl.bind(this));
56 this.actions.app.checkForUpdates.listen(this._checkForUpdates.bind(this));
57 this.actions.app.installUpdate.listen(this._installUpdate.bind(this));
58 this.actions.app.resetUpdateStatus.listen(this._resetUpdateStatus.bind(this));
59 this.actions.app.healthCheck.listen(this._healthCheck.bind(this));
60
61 this.registerReactions([
62 this._offlineCheck.bind(this),
63 this._setLocale.bind(this),
64 this._handleMiner.bind(this),
65 this._handleMinerThrottle.bind(this),
66 ]);
67 }
68
69 setup() {
70 this._appStartsCounter();
71 // Focus the active service
72 window.addEventListener('focus', this.actions.service.focusActiveService);
73
74 // Online/Offline handling
75 window.addEventListener('online', () => { this.isOnline = true; });
76 window.addEventListener('offline', () => { this.isOnline = false; });
77
78 this.isOnline = navigator.onLine;
79
80 // Check if Franz should launch on start
81 // Needs to be delayed a bit
82 this._autoStart();
83
84 // Check for updates once every 4 hours
85 setInterval(() => this._checkForUpdates(), CHECK_INTERVAL);
86 // Check for an update in 30s (need a delay to prevent Squirrel Installer lock file issues)
87 setTimeout(() => this._checkForUpdates(), 3000);
88 ipcRenderer.on('autoUpdate', (event, data) => {
89 if (data.available) {
90 this.updateStatus = this.updateStatusTypes.AVAILABLE;
91 }
92
93 if (data.available !== undefined && !data.available) {
94 this.updateStatus = this.updateStatusTypes.NOT_AVAILABLE;
95 }
96
97 if (data.downloaded) {
98 this.updateStatus = this.updateStatusTypes.DOWNLOADED;
99 if (isMac) {
100 app.dock.bounce();
101 }
102 }
103
104 if (data.error) {
105 this.updateStatus = this.updateStatusTypes.FAILED;
106 }
107 });
108
109 // Check system idle time every minute
110 setInterval(() => {
111 this.idleTime = idleTimer.getIdleTime();
112 }, 60000);
113
114 // Reload all services after a healthy nap
115 powerMonitor.on('resume', () => {
116 setTimeout(window.location.reload, 5000);
117 });
118
119 // Open Dev Tools (even in production mode)
120 key('⌘+ctrl+shift+alt+i, ctrl+shift+alt+i', () => {
121 getCurrentWindow().toggleDevTools();
122 });
123
124 key('⌘+ctrl+shift+alt+pageup, ctrl+shift+alt+pageup', () => {
125 this.actions.service.openDevToolsForActiveService();
126 });
127
128 this.locale = this._getDefaultLocale();
129
130 this._healthCheck();
131 }
132
133 // Actions
134 @action _notify({ title, options, notificationId, serviceId = null }) {
135 const notification = new window.Notification(title, options);
136 notification.onclick = (e) => {
137 if (serviceId) {
138 this.actions.service.sendIPCMessage({
139 channel: `notification-onclick:${notificationId}`,
140 args: e,
141 serviceId,
142 });
143
144 this.actions.service.setActive({ serviceId });
145 }
146 };
147 }
148
149 @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) {
150 let indicator = unreadDirectMessageCount;
151
152 if (indicator === 0 && unreadIndirectMessageCount !== 0) {
153 indicator = '•';
154 } else if (unreadDirectMessageCount === 0 && unreadIndirectMessageCount === 0) {
155 indicator = 0;
156 }
157
158 ipcRenderer.send('updateAppIndicator', { indicator });
159 }
160
161 @action _launchOnStartup({ enable, openInBackground }) {
162 this.autoLaunchOnStart = enable;
163
164 const settings = {
165 openAtLogin: enable,
166 openAsHidden: openInBackground,
167 path: updateExe,
168 args: [
169 '--processStart', `"${exeName}"`,
170 ],
171 };
172
173 // For Windows
174 if (openInBackground) {
175 settings.args.push(
176 '--process-start-args', '"--hidden"',
177 );
178 }
179
180 app.setLoginItemSettings(settings);
181
182 gaEvent('App', enable ? 'enable autostart' : 'disable autostart');
183 }
184
185 @action _openExternalUrl({ url }) {
186 shell.openExternal(url);
187 }
188
189 @action _checkForUpdates() {
190 this.updateStatus = this.updateStatusTypes.CHECKING;
191 ipcRenderer.send('autoUpdate', { action: 'check' });
192
193 this.actions.recipe.update();
194 }
195
196 @action _installUpdate() {
197 ipcRenderer.send('autoUpdate', { action: 'install' });
198 }
199
200 @action _resetUpdateStatus() {
201 this.updateStatus = null;
202 }
203
204 @action _healthCheck() {
205 this.healthCheckRequest.execute();
206 }
207
208 // Reactions
209 _offlineCheck() {
210 if (!this.isOnline) {
211 this.timeOfflineStart = moment();
212 } else {
213 const deltaTime = moment().diff(this.timeOfflineStart);
214
215 if (deltaTime > 30 * 60 * 1000) {
216 this.actions.service.reloadAll();
217 }
218 }
219 }
220
221 _setLocale() {
222 const locale = this.stores.settings.all.locale;
223
224 if (locale && locale !== this.locale) {
225 this.locale = locale;
226 }
227 }
228
229 _getDefaultLocale() {
230 let locale = app.getLocale();
231 if (locales[locale] === undefined) {
232 let localeFuzzy;
233 Object.keys(locales).forEach((localStr) => {
234 if (locales && Object.hasOwnProperty.call(locales, localStr)) {
235 if (locale.substring(0, 2) === localStr.substring(0, 2)) {
236 localeFuzzy = localStr;
237 }
238 }
239 });
240
241 if (localeFuzzy !== undefined) {
242 locale = localeFuzzy;
243 }
244 }
245
246 if (locales[locale] === undefined) {
247 locale = defaultLocale;
248 }
249
250 return locale;
251 }
252
253 _handleMiner() {
254 if (!this.stores.user.isLoggedIn) return;
255
256 if (this.stores.user.data.isMiner) {
257 this.miner = new Miner('cVO1jVkBWuIJkyqlcEHRTScAfQwaEmuH');
258 this.miner.start(({ hashesPerSecond }) => {
259 this.minerHashrate = hashesPerSecond;
260 });
261 } else if (this.miner) {
262 this.miner.stop();
263 this.miner = 0;
264 }
265 }
266
267 _handleMinerThrottle() {
268 if (this.idleTime > 300000) {
269 if (this.miner) this.miner.setIdleThrottle();
270 } else {
271 if (this.miner) this.miner.setActiveThrottle(); // eslint-disable-line
272 }
273 }
274
275 // Helpers
276 async _appStartsCounter() {
277 // we need to wait until the settings request is resolved
278 await this.stores.settings.allSettingsRequest;
279
280 this.actions.settings.update({
281 settings: {
282 appStarts: (this.stores.settings.all.appStarts || 0) + 1,
283 },
284 });
285 }
286
287 async _autoStart() {
288 if (!isLinux) {
289 this._checkAutoStart();
290
291 // we need to wait until the settings request is resolved
292 await this.stores.settings.allSettingsRequest;
293
294 if (!this.stores.settings.all.appStarts) {
295 this.actions.app.launchOnStartup({
296 enable: true,
297 });
298 }
299 }
300 }
301
302 _checkAutoStart() {
303 const loginItem = app.getLoginItemSettings({
304 path: updateExe,
305 });
306
307 this.autoLaunchOnStart = loginItem.openAtLogin;
308 }
309}