diff options
author | Ricardo Cino <ricardo@cino.io> | 2022-06-24 21:25:05 +0200 |
---|---|---|
committer | Vijay Aravamudhan <vraravam@users.noreply.github.com> | 2022-06-25 05:50:00 +0530 |
commit | 2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd (patch) | |
tree | 7c0172945f962609637d03e7de885a254dbec8a4 /src/stores/UserStore.js | |
parent | chore: improve todo menu behaviour on fresh install (#359) (diff) | |
download | ferdium-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.js | 405 |
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 @@ | |||
1 | import { observable, computed, action } from 'mobx'; | ||
2 | import moment from 'moment'; | ||
3 | import jwt from 'jsonwebtoken'; | ||
4 | import localStorage from 'mobx-localstorage'; | ||
5 | import { ipcRenderer } from 'electron'; | ||
6 | |||
7 | import { TODOS_PARTITION_ID } from '../config'; | ||
8 | import { isDevMode } from '../environment-remote'; | ||
9 | import Store from './lib/Store'; | ||
10 | import Request from './lib/Request'; | ||
11 | import CachedRequest from './lib/CachedRequest'; | ||
12 | |||
13 | const debug = require('../preload-safe-debug')('Ferdium:UserStore'); | ||
14 | |||
15 | // TODO: split stores into UserStore and AuthStore | ||
16 | export 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 | } | ||