aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/UserStore.js
diff options
context:
space:
mode:
authorLibravatar Ricardo Cino <ricardo@cino.io>2022-06-24 21:25:05 +0200
committerLibravatar Vijay Aravamudhan <vraravam@users.noreply.github.com>2022-06-25 05:50:00 +0530
commit2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd (patch)
tree7c0172945f962609637d03e7de885a254dbec8a4 /src/stores/UserStore.js
parentchore: improve todo menu behaviour on fresh install (#359) (diff)
downloadferdium-app-2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd.tar.gz
ferdium-app-2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd.tar.zst
ferdium-app-2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd.zip
chore: convert the last few stores to typescript
Diffstat (limited to 'src/stores/UserStore.js')
-rw-r--r--src/stores/UserStore.js405
1 files changed, 0 insertions, 405 deletions
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
deleted file mode 100644
index 661c03e2c..000000000
--- a/src/stores/UserStore.js
+++ /dev/null
@@ -1,405 +0,0 @@
1import { observable, computed, action } from 'mobx';
2import moment from 'moment';
3import jwt from 'jsonwebtoken';
4import localStorage from 'mobx-localstorage';
5import { ipcRenderer } from 'electron';
6
7import { TODOS_PARTITION_ID } from '../config';
8import { isDevMode } from '../environment-remote';
9import Store from './lib/Store';
10import Request from './lib/Request';
11import CachedRequest from './lib/CachedRequest';
12
13const debug = require('../preload-safe-debug')('Ferdium:UserStore');
14
15// TODO: split stores into UserStore and AuthStore
16export default class UserStore extends Store {
17 BASE_ROUTE = '/auth';
18
19 WELCOME_ROUTE = `${this.BASE_ROUTE}/welcome`;
20
21 LOGIN_ROUTE = `${this.BASE_ROUTE}/login`;
22
23 LOGOUT_ROUTE = `${this.BASE_ROUTE}/logout`;
24
25 SIGNUP_ROUTE = `${this.BASE_ROUTE}/signup`;
26
27 SETUP_ROUTE = `${this.BASE_ROUTE}/signup/setup`;
28
29 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`;
30
31 INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`;
32
33 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`;
34
35 CHANGE_SERVER_ROUTE = `${this.BASE_ROUTE}/server`;
36
37 @observable loginRequest = new Request(this.api.user, 'login');
38
39 @observable signupRequest = new Request(this.api.user, 'signup');
40
41 @observable passwordRequest = new Request(this.api.user, 'password');
42
43 @observable inviteRequest = new Request(this.api.user, 'invite');
44
45 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo');
46
47 @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo');
48
49 @observable getLegacyServicesRequest = new CachedRequest(
50 this.api.user,
51 'getLegacyServices',
52 );
53
54 @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete');
55
56 @observable isImportLegacyServicesExecuting = false;
57
58 @observable isImportLegacyServicesCompleted = false;
59
60 @observable isLoggingOut = false;
61
62 @observable id;
63
64 @observable authToken = localStorage.getItem('authToken') || null;
65
66 @observable accountType;
67
68 @observable hasCompletedSignup = false;
69
70 @observable userData = {};
71
72 @observable actionStatus = [];
73
74 logoutReasonTypes = {
75 SERVER: 'SERVER',
76 };
77
78 @observable logoutReason = null;
79
80 fetchUserInfoInterval = null;
81
82 constructor(...args) {
83 super(...args);
84
85 // Register action handlers
86 this.actions.user.login.listen(this._login.bind(this));
87 this.actions.user.retrievePassword.listen(
88 this._retrievePassword.bind(this),
89 );
90 this.actions.user.logout.listen(this._logout.bind(this));
91 this.actions.user.signup.listen(this._signup.bind(this));
92 this.actions.user.invite.listen(this._invite.bind(this));
93 this.actions.user.update.listen(this._update.bind(this));
94 this.actions.user.resetStatus.listen(this._resetStatus.bind(this));
95 this.actions.user.importLegacyServices.listen(
96 this._importLegacyServices.bind(this),
97 );
98 this.actions.user.delete.listen(this._delete.bind(this));
99
100 // Reactions
101 this.registerReactions([
102 this._requireAuthenticatedUser.bind(this),
103 this._getUserData.bind(this),
104 ]);
105 }
106
107 setup() {
108 // Data migration
109 this._migrateUserLocale();
110 }
111
112 // Routes
113 get loginRoute() {
114 return this.LOGIN_ROUTE;
115 }
116
117 get logoutRoute() {
118 return this.LOGOUT_ROUTE;
119 }
120
121 get signupRoute() {
122 return this.SIGNUP_ROUTE;
123 }
124
125 get setupRoute() {
126 return this.SETUP_ROUTE;
127 }
128
129 get inviteRoute() {
130 return this.INVITE_ROUTE;
131 }
132
133 get importRoute() {
134 return this.IMPORT_ROUTE;
135 }
136
137 get passwordRoute() {
138 return this.PASSWORD_ROUTE;
139 }
140
141 get changeServerRoute() {
142 return this.CHANGE_SERVER_ROUTE;
143 }
144
145 // Data
146 @computed get isLoggedIn() {
147 return Boolean(localStorage.getItem('authToken'));
148 }
149
150 @computed get isTokenExpired() {
151 if (!this.authToken) return false;
152
153 const { tokenExpiry } = this._parseToken(this.authToken);
154 return this.authToken !== null && moment(tokenExpiry).isBefore(moment());
155 }
156
157 @computed get data() {
158 if (!this.isLoggedIn) return {};
159
160 return this.getUserInfoRequest.execute().result || {};
161 }
162
163 @computed get team() {
164 return this.data.team || null;
165 }
166
167 @computed get legacyServices() {
168 return this.getLegacyServicesRequest.execute() || {};
169 }
170
171 // Actions
172 @action async _login({ email, password }) {
173 const authToken = await this.loginRequest.execute(email, password)._promise;
174 this._setUserData(authToken);
175
176 this.stores.router.push('/');
177 }
178
179 @action _tokenLogin(authToken) {
180 this._setUserData(authToken);
181
182 this.stores.router.push('/');
183 }
184
185 @action async _signup({
186 firstname,
187 lastname,
188 email,
189 password,
190 accountType,
191 company,
192 plan,
193 currency,
194 }) {
195 const authToken = await this.signupRequest.execute({
196 firstname,
197 lastname,
198 email,
199 password,
200 accountType,
201 company,
202 locale: this.stores.app.locale,
203 plan,
204 currency,
205 });
206
207 this.hasCompletedSignup = true;
208
209 this._setUserData(authToken);
210
211 this.stores.router.push(this.SETUP_ROUTE);
212 }
213
214 @action async _retrievePassword({ email }) {
215 const request = this.passwordRequest.execute(email);
216
217 await request._promise;
218 this.actionStatus = request.result.status || [];
219 }
220
221 @action async _invite({ invites }) {
222 const data = invites.filter(invite => invite.email !== '');
223
224 const response = await this.inviteRequest.execute(data)._promise;
225
226 this.actionStatus = response.status || [];
227
228 // we do not wait for a server response before redirecting the user ONLY DURING SIGNUP
229 if (this.stores.router.location.pathname.includes(this.INVITE_ROUTE)) {
230 this.stores.router.push('/');
231 }
232 }
233
234 @action async _update({ userData }) {
235 if (!this.isLoggedIn) return;
236
237 const response = await this.updateUserInfoRequest.execute(userData)
238 ._promise;
239
240 this.getUserInfoRequest.patch(() => response.data);
241 this.actionStatus = response.status || [];
242 }
243
244 @action _resetStatus() {
245 this.actionStatus = [];
246 }
247
248 @action _logout() {
249 // workaround mobx issue
250 localStorage.removeItem('authToken');
251 window.localStorage.removeItem('authToken');
252
253 this.getUserInfoRequest.invalidate().reset();
254 this.authToken = null;
255
256 this.stores.services.allServicesRequest.invalidate().reset();
257
258 if (this.stores.todos.isTodosEnabled) {
259 ipcRenderer.send('clear-storage-data', { sessionId: TODOS_PARTITION_ID });
260 }
261 }
262
263 @action async _importLegacyServices({ services }) {
264 this.isImportLegacyServicesExecuting = true;
265
266 // Reduces recipe duplicates
267 const recipes = services
268 .filter(
269 (obj, pos, arr) =>
270 arr.map(mapObj => mapObj.recipe.id).indexOf(obj.recipe.id) === pos,
271 )
272 .map(s => s.recipe.id);
273
274 // Install recipes
275 for (const recipe of recipes) {
276 // eslint-disable-next-line no-await-in-loop
277 await this.stores.recipes._install({ recipeId: recipe });
278 }
279
280 for (const service of services) {
281 this.actions.service.createFromLegacyService({
282 data: service,
283 });
284 // eslint-disable-next-line no-await-in-loop
285 await this.stores.services.createServiceRequest._promise;
286 }
287
288 this.isImportLegacyServicesExecuting = false;
289 this.isImportLegacyServicesCompleted = true;
290 }
291
292 @action async _delete() {
293 this.deleteAccountRequest.execute();
294 }
295
296 // This is a mobx autorun which forces the user to login if not authenticated
297 _requireAuthenticatedUser = () => {
298 if (this.isTokenExpired) {
299 this._logout();
300 }
301
302 const { router } = this.stores;
303 const currentRoute = window.location.hash;
304 if (!this.isLoggedIn && currentRoute.includes('token=')) {
305 router.push(this.WELCOME_ROUTE);
306 const token = currentRoute.split('=')[1];
307
308 const data = this._parseToken(token);
309 if (data) {
310 // Give this some time to sink
311 setTimeout(() => {
312 this._tokenLogin(token);
313 }, 1000);
314 }
315 } else if (!this.isLoggedIn && !currentRoute.includes(this.BASE_ROUTE)) {
316 router.push(this.WELCOME_ROUTE);
317 } else if (this.isLoggedIn && currentRoute === this.LOGOUT_ROUTE) {
318 this.actions.user.logout();
319 router.push(this.LOGIN_ROUTE);
320 } else if (
321 this.isLoggedIn &&
322 currentRoute.includes(this.BASE_ROUTE) &&
323 (this.hasCompletedSignup || this.hasCompletedSignup === null) &&
324 !isDevMode
325 ) {
326 this.stores.router.push('/');
327 }
328 };
329
330 // Reactions
331 async _getUserData() {
332 if (this.isLoggedIn) {
333 let data;
334 try {
335 data = await this.getUserInfoRequest.execute()._promise;
336 } catch {
337 return false;
338 }
339
340 // We need to set the beta flag for the SettingsStore
341 this.actions.settings.update({
342 type: 'app',
343 data: {
344 beta: data.beta,
345 locale: data.locale,
346 },
347 });
348 }
349 }
350
351 // Helpers
352 _parseToken(authToken) {
353 try {
354 const decoded = jwt.decode(authToken);
355
356 return {
357 id: decoded.userId,
358 tokenExpiry: moment.unix(decoded.exp).toISOString(),
359 authToken,
360 };
361 } catch {
362 this._logout();
363 return false;
364 }
365 }
366
367 _setUserData(authToken) {
368 const data = this._parseToken(authToken);
369 if (data.authToken) {
370 localStorage.setItem('authToken', data.authToken);
371
372 this.authToken = data.authToken;
373 this.id = data.id;
374 } else {
375 this.authToken = null;
376 this.id = null;
377 }
378 }
379
380 getAuthURL(url) {
381 const parsedUrl = new URL(url);
382 const params = new URLSearchParams(parsedUrl.search.slice(1));
383
384 params.append('authToken', this.authToken);
385
386 return `${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`;
387 }
388
389 async _migrateUserLocale() {
390 try {
391 await this.getUserInfoRequest._promise;
392 } catch {
393 return false;
394 }
395
396 if (!this.data.locale) {
397 debug('Migrate "locale" to user data');
398 this.actions.user.update({
399 userData: {
400 locale: this.stores.app.locale,
401 },
402 });
403 }
404 }
405}