From d9502c7516bc2d4ae467c6ea8a2e4816b0885f37 Mon Sep 17 00:00:00 2001 From: muhamedsalih-tw <104364298+muhamedsalih-tw@users.noreply.github.com> Date: Thu, 17 Nov 2022 05:45:39 +0530 Subject: Transfrom workspace components to ts (#775) --- src/stores/AppStore.ts | 2 +- src/stores/FeaturesStore.ts | 2 +- src/stores/GlobalErrorStore.ts | 27 +++---- src/stores/RecipesStore.ts | 10 +-- src/stores/RequestStore.ts | 3 + src/stores/ServicesStore.ts | 10 +-- src/stores/UserStore.ts | 17 +++-- src/stores/lib/CachedRequest.js | 121 ------------------------------ src/stores/lib/CachedRequest.ts | 126 +++++++++++++++++++++++++++++++ src/stores/lib/Request.js | 155 --------------------------------------- src/stores/lib/Request.ts | 159 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 318 insertions(+), 314 deletions(-) delete mode 100644 src/stores/lib/CachedRequest.js create mode 100644 src/stores/lib/CachedRequest.ts delete mode 100644 src/stores/lib/Request.js create mode 100644 src/stores/lib/Request.ts (limited to 'src/stores') diff --git a/src/stores/AppStore.ts b/src/stores/AppStore.ts index aab279e59..2db90bfa0 100644 --- a/src/stores/AppStore.ts +++ b/src/stores/AppStore.ts @@ -494,7 +494,7 @@ export default class AppStore extends TypedStore { ), ); - await clearAppCache._promise; + await clearAppCache.promise; await sleep(ms('1s')); diff --git a/src/stores/FeaturesStore.ts b/src/stores/FeaturesStore.ts index ed0c6c17b..5f43ccf84 100644 --- a/src/stores/FeaturesStore.ts +++ b/src/stores/FeaturesStore.ts @@ -45,7 +45,7 @@ export default class FeaturesStore extends TypedStore { this._monitorLoginStatus.bind(this), ]); - await this.featuresRequest._promise; + await this.featuresRequest.promise; setTimeout(this._setupFeatures.bind(this), 1); } diff --git a/src/stores/GlobalErrorStore.ts b/src/stores/GlobalErrorStore.ts index c42e9a4af..be86563d0 100644 --- a/src/stores/GlobalErrorStore.ts +++ b/src/stores/GlobalErrorStore.ts @@ -1,4 +1,5 @@ import { observable, action, makeObservable } from 'mobx'; +import { Response } from 'electron'; import { Actions } from '../actions/lib/actions'; import { ApiInterface } from '../api'; import { Stores } from '../@types/stores.types'; @@ -11,12 +12,8 @@ interface Message { message?: string; status?: number; }; - request?: { - result: any; - wasExecuted: any; - method: any; - }; - response?: any; + request?: Request; + response?: Response; server?: any; info?: any; url?: string; @@ -28,7 +25,7 @@ export default class GlobalErrorStore extends TypedStore { @observable messages: Message[] = []; - @observable response: object = {}; + @observable response: Response = {} as Response; // TODO: Get rid of the @ts-ignores in this function. constructor(stores: Stores, api: ApiInterface, actions: Actions) { @@ -85,21 +82,15 @@ export default class GlobalErrorStore extends TypedStore { } } - @action _handleRequests = async (request: { - isError: any; - error: { json: () => object | PromiseLike }; - result: any; - wasExecuted: any; - _method: any; - }): Promise => { + @action _handleRequests = async (request: Request): Promise => { if (request.isError) { this.error = request.error; - if (request.error.json) { + if (request.error && request.error.json) { try { this.response = await request.error.json(); } catch { - this.response = {}; + this.response = {} as Response; } if (this.error?.status === 401) { window['ferdium'].stores.app.authRequestFailed = true; @@ -111,8 +102,8 @@ export default class GlobalErrorStore extends TypedStore { request: { result: request.result, wasExecuted: request.wasExecuted, - method: request._method, - }, + method: request.method, + } as Request, error: this.error, response: this.response, server: window['ferdium'].stores.settings.app.server, diff --git a/src/stores/RecipesStore.ts b/src/stores/RecipesStore.ts index 25304e97c..07f1343f8 100644 --- a/src/stores/RecipesStore.ts +++ b/src/stores/RecipesStore.ts @@ -74,8 +74,8 @@ export default class RecipesStore extends TypedStore { // Actions async _install({ recipeId }): Promise { - const recipe = await this.installRecipeRequest.execute(recipeId)._promise; - await this.allRecipesRequest.invalidate({ immediately: true })._promise; + const recipe = await this.installRecipeRequest.execute(recipeId).promise; + await this.allRecipesRequest.invalidate({ immediately: true }).promise; return recipe; } @@ -128,7 +128,7 @@ export default class RecipesStore extends TypedStore { const update = updates[i]; this.actions.recipe.install({ recipeId: update }); - await this.installRecipeRequest._promise; + await this.installRecipeRequest.promise; this.installRecipeRequest.reset(); @@ -158,10 +158,10 @@ export default class RecipesStore extends TypedStore { debug(`Recipe ${recipeId} is not installed, trying to install it`); const recipe = await this.installRecipeRequest.execute(recipeId) - ._promise; + .promise; if (recipe) { await this.allRecipesRequest.invalidate({ immediately: true }) - ._promise; + .promise; router.push(`/settings/services/add/${recipeId}`); } else { router.push('/settings/recipes'); diff --git a/src/stores/RequestStore.ts b/src/stores/RequestStore.ts index 279615e50..807f2d126 100644 --- a/src/stores/RequestStore.ts +++ b/src/stores/RequestStore.ts @@ -37,6 +37,9 @@ export default class RequestStore extends TypedStore { ); this.registerReactions([this._autoRetry.bind(this)]); + + this.userInfoRequest = {} as CachedRequest; + this.servicesRequest = {} as CachedRequest; } async setup(): Promise { diff --git a/src/stores/ServicesStore.ts b/src/stores/ServicesStore.ts index 74810b81f..4fdd9d5ad 100644 --- a/src/stores/ServicesStore.ts +++ b/src/stores/ServicesStore.ts @@ -477,7 +477,7 @@ export default class ServicesStore extends TypedStore { : this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); const response = await this.createServiceRequest.execute(recipeId, data) - ._promise; + .promise; this.allServicesRequest.patch(result => { if (!result) return; @@ -536,7 +536,7 @@ export default class ServicesStore extends TypedStore { const newData = serviceData; if (serviceData.iconFile) { - await request._promise; + await request.promise; newData.iconUrl = request.result.data.iconUrl; newData.hasCustomUploadedIcon = true; @@ -562,7 +562,7 @@ export default class ServicesStore extends TypedStore { ); }); - await request._promise; + await request.promise; this.actionStatus = request.result.status; if (service.isEnabled) { @@ -596,7 +596,7 @@ export default class ServicesStore extends TypedStore { remove(result, (c: Service) => c.id === serviceId); }); - await request._promise; + await request.promise; this.actionStatus = request.result.status; } @@ -637,7 +637,7 @@ export default class ServicesStore extends TypedStore { @action async _clearCache({ serviceId }) { this.clearCacheRequest.reset(); const request = this.clearCacheRequest.execute(serviceId); - await request._promise; + await request.promise; } @action _setIsActive(service: Service, state: boolean): void { diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index c5e67c966..6c8f8f20b 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -187,7 +187,7 @@ export default class UserStore extends TypedStore { // Actions @action async _login({ email, password }): Promise { - const authToken = await this.loginRequest.execute(email, password)._promise; + const authToken = await this.loginRequest.execute(email, password).promise; this._setUserData(authToken); this.stores.router.push('/'); @@ -209,6 +209,8 @@ export default class UserStore extends TypedStore { plan, currency, }): Promise { + // TODO - [TS DEBT] Need to find a way proper to implement promise's then and catch in request class + // @ts-ignore const authToken = await this.signupRequest.execute({ firstname, lastname, @@ -231,14 +233,14 @@ export default class UserStore extends TypedStore { @action async _retrievePassword({ email }): Promise { const request = this.passwordRequest.execute(email); - await request._promise; + await request.promise; this.actionStatus = request.result.status || []; } @action async _invite({ invites }): Promise { const data = invites.filter(invite => invite.email !== ''); - const response = await this.inviteRequest.execute(data)._promise; + const response = await this.inviteRequest.execute(data).promise; this.actionStatus = response.status || []; @@ -251,8 +253,7 @@ export default class UserStore extends TypedStore { @action async _update({ userData }): Promise { if (!this.isLoggedIn) return; - const response = await this.updateUserInfoRequest.execute(userData) - ._promise; + const response = await this.updateUserInfoRequest.execute(userData).promise; this.getUserInfoRequest.patch(() => response.data); this.actionStatus = response.status || []; @@ -299,7 +300,7 @@ export default class UserStore extends TypedStore { data: service, }); // eslint-disable-next-line no-await-in-loop - await this.stores.services.createServiceRequest._promise; + await this.stores.services.createServiceRequest.promise; } this.isImportLegacyServicesExecuting = false; @@ -349,7 +350,7 @@ export default class UserStore extends TypedStore { if (this.isLoggedIn) { let data; try { - data = await this.getUserInfoRequest.execute()._promise; + data = await this.getUserInfoRequest.execute().promise; } catch { return; } @@ -406,7 +407,7 @@ export default class UserStore extends TypedStore { async _migrateUserLocale(): Promise { try { - await this.getUserInfoRequest._promise; + await this.getUserInfoRequest.promise; } catch { return; } diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.js deleted file mode 100644 index a6dd47f7d..000000000 --- a/src/stores/lib/CachedRequest.js +++ /dev/null @@ -1,121 +0,0 @@ -import { action } from 'mobx'; -import { isEqual, remove } from 'lodash'; -import Request from './Request'; - -export default class CachedRequest extends Request { - _apiCalls = []; - - _isInvalidated = true; - - execute(...callArgs) { - // Do not continue if this request is already loading - if (this._isWaitingForResponse) return this; - - // Very simple caching strategy -> only continue if the call / args changed - // or the request was invalidated manually from outside - const existingApiCall = this._findApiCall(callArgs); - - // Invalidate if new or different api call will be done - if (existingApiCall && existingApiCall !== this._currentApiCall) { - this._isInvalidated = true; - this._currentApiCall = existingApiCall; - } else if (!existingApiCall) { - this._isInvalidated = true; - this._currentApiCall = this._addApiCall(callArgs); - } - - // Do not continue if this request is not invalidated (see above) - if (!this._isInvalidated) return this; - - // This timeout is necessary to avoid warnings from mobx - // regarding triggering actions as side-effect of getters - setTimeout( - action(() => { - this.isExecuting = true; - // Apply the previous result from this call immediately (cached) - if (existingApiCall) { - this.result = existingApiCall.result; - } - }), - 0, - ); - - // Issue api call & save it as promise that is handled to update the results of the operation - this._promise = new Promise(resolve => { - this._api[this._method](...callArgs) - .then(result => { - setTimeout( - action(() => { - this.result = result; - if (this._currentApiCall) this._currentApiCall.result = result; - this.isExecuting = false; - this.isError = false; - this.wasExecuted = true; - this._isInvalidated = false; - this._isWaitingForResponse = false; - this._triggerHooks(); - resolve(result); - }), - 1, - ); - return result; - }) - .catch( - action(error => { - setTimeout( - action(() => { - this.error = error; - this.isExecuting = false; - this.isError = true; - this.wasExecuted = true; - this._isWaitingForResponse = false; - this._triggerHooks(); - // reject(error); - }), - 1, - ); - }), - ); - }); - - this._isWaitingForResponse = true; - return this; - } - - // eslint-disable-next-line unicorn/no-object-as-default-parameter - invalidate(options = { immediately: false }) { - this._isInvalidated = true; - if (options.immediately && this._currentApiCall) { - return this.execute(...this._currentApiCall.args); - } - return this; - } - - patch(modify) { - return new Promise(resolve => { - setTimeout( - action(() => { - const override = modify(this.result); - if (override !== undefined) this.result = override; - if (this._currentApiCall) this._currentApiCall.result = this.result; - resolve(this); - }), - 0, - ); - }); - } - - removeCacheForCallWith(...args) { - remove(this._apiCalls, c => isEqual(c.args, args)); - } - - _addApiCall(args) { - const newCall = { args, result: null }; - this._apiCalls.push(newCall); - return newCall; - } - - _findApiCall(args) { - return this._apiCalls.find(c => isEqual(c.args, args)); - } -} diff --git a/src/stores/lib/CachedRequest.ts b/src/stores/lib/CachedRequest.ts new file mode 100644 index 000000000..25cc365e2 --- /dev/null +++ b/src/stores/lib/CachedRequest.ts @@ -0,0 +1,126 @@ +import { action } from 'mobx'; +import { isEqual, remove } from 'lodash'; +import Request from './Request'; + +export default class CachedRequest extends Request { + _apiCalls: any[] = []; + + _isInvalidated = true; + + execute(...callArgs): this { + // Do not continue if this request is already loading + if (this.isWaitingForResponse) { + return this; + } + + // Very simple caching strategy -> only continue if the call / args changed + // or the request was invalidated manually from outside + const existingApiCall = this._findApiCall(callArgs); + + // Invalidate if new or different api call will be done + if (existingApiCall && existingApiCall !== this.currentApiCall) { + this._isInvalidated = true; + this.currentApiCall = existingApiCall; + } else if (!existingApiCall) { + this._isInvalidated = true; + this.currentApiCall = this._addApiCall(callArgs); + } + + // Do not continue if this request is not invalidated (see above) + if (!this._isInvalidated) { + return this; + } + + // This timeout is necessary to avoid warnings from mobx + // regarding triggering actions as side-effect of getters + setTimeout( + action(() => { + this.isExecuting = true; + // Apply the previous result from this call immediately (cached) + if (existingApiCall) { + this.result = existingApiCall.result; + } + }), + 0, + ); + + // Issue api call & save it as promise that is handled to update the results of the operation + this.promise = new Promise(resolve => { + this.api[this.method](...callArgs) + .then(result => { + setTimeout( + action(() => { + this.result = result; + if (this.currentApiCall) this.currentApiCall.result = result; + this.isExecuting = false; + this.isError = false; + this.wasExecuted = true; + this._isInvalidated = false; + this.isWaitingForResponse = false; + this._triggerHooks(); + resolve(result); + }), + 1, + ); + return result; + }) + .catch( + action(error => { + setTimeout( + action(() => { + this.error = error; + this.isExecuting = false; + this.isError = true; + this.wasExecuted = true; + this.isWaitingForResponse = false; + this._triggerHooks(); + // reject(error); + }), + 1, + ); + }), + ); + }); + + this.isWaitingForResponse = true; + return this; + } + + static defaultOptions = { immediately: false }; + + invalidate(options = CachedRequest.defaultOptions): this { + this._isInvalidated = true; + if (options.immediately && this.currentApiCall) { + return this.execute(...this.currentApiCall.args); + } + return this; + } + + patch(modify): Promise { + return new Promise(resolve => { + setTimeout( + action(() => { + const override = modify(this.result); + if (override !== undefined) this.result = override; + if (this.currentApiCall) this.currentApiCall.result = this.result; + resolve(this); + }), + 0, + ); + }); + } + + removeCacheForCallWith(...args: any): void { + remove(this._apiCalls, c => isEqual(c.args, args)); + } + + _addApiCall(args: any) { + const newCall = { args, result: null }; + this._apiCalls.push(newCall); + return newCall; + } + + _findApiCall(args: any) { + return this._apiCalls.find(c => isEqual(c.args, args)); + } +} diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js deleted file mode 100644 index 60c943a42..000000000 --- a/src/stores/lib/Request.js +++ /dev/null @@ -1,155 +0,0 @@ -import { observable, action, computed, makeObservable } from 'mobx'; -import { isEqual } from 'lodash/fp'; - -export default class Request { - static _hooks = []; - - static registerHook(hook) { - Request._hooks.push(hook); - } - - @observable result = null; - - @observable error = null; - - @observable isExecuting = false; - - @observable isError = false; - - @observable wasExecuted = false; - - @action _reset() { - this.error = null; - this.result = null; - this.isExecuting = false; - this.isError = false; - this.wasExecuted = false; - this._isWaitingForResponse = false; - this._promise = Promise; - - return this; - } - - _promise = Promise; - - _api = {}; - - _method = ''; - - _isWaitingForResponse = false; - - _currentApiCall = null; - - constructor(api, method) { - makeObservable(this); - - this._api = api; - this._method = method; - } - - execute(...callArgs) { - // Do not continue if this request is already loading - if (this._isWaitingForResponse) return this; - - if (!this._api[this._method]) { - throw new Error( - `Missing method <${this._method}> on api object:`, - this._api, - ); - } - - // This timeout is necessary to avoid warnings from mobx - // regarding triggering actions as side-effect of getters - setTimeout( - action(() => { - this.isExecuting = true; - }), - 0, - ); - - // Issue api call & save it as promise that is handled to update the results of the operation - this._promise = new Promise((resolve, reject) => { - this._api[this._method](...callArgs) - .then(result => { - setTimeout( - action(() => { - this.error = null; - this.result = result; - if (this._currentApiCall) this._currentApiCall.result = result; - this.isExecuting = false; - this.isError = false; - this.wasExecuted = true; - this._isWaitingForResponse = false; - this._triggerHooks(); - resolve(result); - }), - 1, - ); - return result; - }) - .catch( - action(error => { - setTimeout( - action(() => { - this.error = error; - this.isExecuting = false; - this.isError = true; - this.wasExecuted = true; - this._isWaitingForResponse = false; - this._triggerHooks(); - reject(error); - }), - 1, - ); - }), - ); - }); - - this._isWaitingForResponse = true; - this._currentApiCall = { args: callArgs, result: null }; - return this; - } - - reload() { - const args = this._currentApiCall ? this._currentApiCall.args : []; - this.error = null; - return this.execute(...args); - } - - retry = () => this.reload(); - - isExecutingWithArgs(...args) { - return ( - this.isExecuting && - this._currentApiCall && - isEqual(this._currentApiCall.args, args) - ); - } - - @computed get isExecutingFirstTime() { - return !this.wasExecuted && this.isExecuting; - } - - /* eslint-disable unicorn/no-thenable */ - then(...args) { - if (!this._promise) - throw new Error( - 'You have to call Request::execute before you can access it as promise', - ); - return this._promise.then(...args); - } - - catch(...args) { - if (!this._promise) - throw new Error( - 'You have to call Request::execute before you can access it as promise', - ); - return this._promise.catch(...args); - } - - _triggerHooks() { - for (const hook of Request._hooks) hook(this); - } - - reset = () => this._reset(); -} diff --git a/src/stores/lib/Request.ts b/src/stores/lib/Request.ts new file mode 100644 index 000000000..f9424ac99 --- /dev/null +++ b/src/stores/lib/Request.ts @@ -0,0 +1,159 @@ +import { observable, action, computed, makeObservable } from 'mobx'; +import { isEqual } from 'lodash/fp'; + +type Hook = (request: Request) => void; + +export default class Request { + static _hooks: Hook[] = []; + + static registerHook(hook: Hook) { + Request._hooks.push(hook); + } + + @observable result: any = null; + + @observable error: any = null; + + @observable isExecuting = false; + + @observable isError = false; + + @observable wasExecuted = false; + + promise: any = Promise; + + protected api: any = {}; + + method = ''; + + protected isWaitingForResponse = false; + + protected currentApiCall: any = null; + + retry = () => this.reload(); + + reset = () => this._reset(); + + constructor(api, method) { + makeObservable(this); + + this.api = api; + this.method = method; + } + + @action _reset(): this { + this.error = null; + this.result = null; + this.isExecuting = false; + this.isError = false; + this.wasExecuted = false; + this.isWaitingForResponse = false; + this.promise = Promise; + + return this; + } + + execute(...callArgs: any[]): this { + // Do not continue if this request is already loading + if (this.isWaitingForResponse) return this; + + if (!this.api[this.method]) { + throw new Error( + `Missing method <${this.method}> on api object:`, + this.api, + ); + } + + // This timeout is necessary to avoid warnings from mobx + // regarding triggering actions as side-effect of getters + setTimeout( + action(() => { + this.isExecuting = true; + }), + 0, + ); + + // Issue api call & save it as promise that is handled to update the results of the operation + this.promise = new Promise((resolve, reject) => { + this.api[this.method](...callArgs) + .then(result => { + setTimeout( + action(() => { + this.error = null; + this.result = result; + if (this.currentApiCall) this.currentApiCall.result = result; + this.isExecuting = false; + this.isError = false; + this.wasExecuted = true; + this.isWaitingForResponse = false; + this._triggerHooks(); + resolve(result); + }), + 1, + ); + return result; + }) + .catch( + action(error => { + setTimeout( + action(() => { + this.error = error; + this.isExecuting = false; + this.isError = true; + this.wasExecuted = true; + this.isWaitingForResponse = false; + this._triggerHooks(); + reject(error); + }), + 1, + ); + }), + ); + }); + + this.isWaitingForResponse = true; + this.currentApiCall = { args: callArgs, result: null }; + return this; + } + + reload(): this { + const args = this.currentApiCall ? this.currentApiCall.args : []; + this.error = null; + return this.execute(...args); + } + + isExecutingWithArgs(...args: any[]): boolean { + return ( + this.isExecuting && + this.currentApiCall && + isEqual(this.currentApiCall.args, args) + ); + } + + @computed get isExecutingFirstTime(): boolean { + return !this.wasExecuted && this.isExecuting; + } + + /* eslint-disable unicorn/no-thenable */ + then(...args: any[]) { + if (!this.promise) + throw new Error( + 'You have to call Request::execute before you can access it as promise', + ); + return this.promise.then(...args); + } + + catch(...args: any[]) { + if (!this.promise) + throw new Error( + 'You have to call Request::execute before you can access it as promise', + ); + return this.promise.catch(...args); + } + + _triggerHooks(): void { + for (const hook of Request._hooks) { + hook(this); + } + } +} -- cgit v1.2.3-70-g09d2