From 62cbf09e6f6d83f0d30d00644ec0c6c3a5f731a7 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 30 Mar 2021 16:24:26 -0400 Subject: [PATCH 01/22] add "immer" (library name) to dictionary --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5b6c03dc1..a378ca012 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "SHOCKWALLET", "Unmount", "autocorrect", + "immer", "luxon", "persistor", "uuidv", From bdd03492395beafa72ba52ba28a74447e54e97e0 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 30 Mar 2021 16:38:26 -0400 Subject: [PATCH 02/22] add "reduxjs" (library name) to dictionary --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index a378ca012..83aacfc1f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "persistor", "uuidv", "qrcode", + "reduxjs", "serv", "videojs", "youtube" From 6e6e80215ad948627eab35f5c2aa48c6ec235534 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 30 Mar 2021 16:41:22 -0400 Subject: [PATCH 03/22] move this file to typescript --- src/actions/{AuthActions.js => AuthActions.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/actions/{AuthActions.js => AuthActions.ts} (100%) diff --git a/src/actions/AuthActions.js b/src/actions/AuthActions.ts similarity index 100% rename from src/actions/AuthActions.js rename to src/actions/AuthActions.ts From 6fe806212593d7129c303ec0fb99befb1c05af1e Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Wed, 31 Mar 2021 12:24:27 -0400 Subject: [PATCH 04/22] add "healthz" (endpoint name) to dictionary --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 83aacfc1f..387228191 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "SHOCKWALLET", "Unmount", "autocorrect", + "healthz", "immer", "luxon", "persistor", From ac85d622c630cc578527b40dd1eea8ad2d8d02a7 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Wed, 31 Mar 2021 17:31:09 -0400 Subject: [PATCH 05/22] store helper for sockets/callbacks inside sagas (should use channels in the future) --- src/store/sagas/_store.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/store/sagas/_store.ts diff --git a/src/store/sagas/_store.ts b/src/store/sagas/_store.ts new file mode 100644 index 000000000..d12af37f2 --- /dev/null +++ b/src/store/sagas/_store.ts @@ -0,0 +1,13 @@ +import { Store, AnyAction } from "redux"; + +import { State } from "../../reducers"; + +type ShockStore = Store; + +let currStore = {} as ShockStore; + +export const _setStore = (store: ShockStore) => { + currStore = store; +}; + +export const getStore = () => currStore; From fe45a6ed66c6e84762a6a1c740eb6b6dc7b9b715 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Wed, 31 Mar 2021 17:38:28 -0400 Subject: [PATCH 06/22] add "shockping" (event name) to dictionary --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 387228191..4013db8fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,10 +11,11 @@ "immer", "luxon", "persistor", - "uuidv", "qrcode", "reduxjs", "serv", + "shockping", + "uuidv", "videojs", "youtube" ] From bb8d3195ecdf0e1e58d589d02bd4654698c4220f Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Wed, 31 Mar 2021 20:05:56 -0400 Subject: [PATCH 07/22] add typings for luxon library --- package-lock.json | 6 ++++++ package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index 62c4cc2ea..b9c096713 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2182,6 +2182,12 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "@types/luxon": { + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.26.2.tgz", + "integrity": "sha512-2pvzy4LuxBMBBLAbml6PDcJPiIeZQ0Hqj3PE31IxkNI250qeoRMDovTrHXeDkIL4auvtarSdpTkLHs+st43EYQ==", + "dev": true + }, "@types/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", diff --git a/package.json b/package.json index 791d701c6..e12ab98db 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ }, "devDependencies": { "@types/classnames": "^2.2.11", + "@types/luxon": "^1.26.2", "@types/react-router-dom": "5.x.x", "@types/socket.io-client": "^1.4.35", "eslint-config-prettier": "^8.1.0", From a652a7ed6034fd2b251c91eec1e3f7ec4aeafb4e Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Wed, 31 Mar 2021 20:10:35 -0400 Subject: [PATCH 08/22] promise related utils --- src/utils/index.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/utils/index.ts b/src/utils/index.ts index ad5470518..4e0531582 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -119,3 +119,27 @@ export const processImageFile = async ( return resizedImage; }; + +export const wait = (ms: number): Promise => + new Promise(r => { + setTimeout(r, ms); + }); + +export const retryOperation = ( + operation: () => Promise, + delay: number, + retries: number +): Promise => + new Promise((resolve, reject) => { + return operation() + .then(resolve) + .catch(reason => { + if (retries > 0) { + return wait(delay) + .then(retryOperation.bind(null, operation, delay, retries - 1)) + .then(resolve) + .catch(reject); + } + return reject(reason); + }); + }); From a9a6154c53ac9116262d5a0c23264d011af5cc3e Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Wed, 31 Mar 2021 20:21:08 -0400 Subject: [PATCH 09/22] util for normalization of timestamps --- src/utils/index.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/utils/index.ts b/src/utils/index.ts index 4e0531582..33f8d632b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -143,3 +143,32 @@ export const retryOperation = ( return reject(reason); }); }); + +/** + * Converts seconds/microseconds timestamps to milliseconds, leaves milliseconds + * timestamps untouched. Works for timestamps no older than 2001. + * @timestamp A timestamp that can be seconds, milliseconds or microseconds. + * Should be no older than 2001. + */ +export function normalizeTimestampToMs(timestamp: number): number { + if (timestamp === 0) { + return timestamp; + } + + const t = timestamp.toString(); + + if (t.length === 10) { + // is seconds + return Number(t) * 1000; + } else if (t.length === 13) { + // is milliseconds + return Number(t); + } else if (t.length === 16) { + // is microseconds + return Number(t) / 1000; + } + + logger.error("normalizeTimestamp() -> could not interpret timestamp"); + + return Number(t); +} From d56bd886611e23c94e4a431711c0c5ed38cd9d3c Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 16:31:08 -0400 Subject: [PATCH 10/22] export type --- src/utils/Error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Error.ts b/src/utils/Error.ts index 8b84a87e1..7eaadfa3d 100644 --- a/src/utils/Error.ts +++ b/src/utils/Error.ts @@ -1,7 +1,7 @@ import { AxiosError } from "axios"; import FieldError, { FieldErrorType } from "./FieldError"; -type MixedError = Error | AxiosError | FieldErrorType; +export type MixedError = Error | AxiosError | FieldErrorType; export const parseError = (error: MixedError) => { if ("response" in error) { From c2c0f14d0e166a478e4c3e15c1fd67c338834d54 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 16:31:18 -0400 Subject: [PATCH 11/22] re-export error utils --- src/utils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/index.ts b/src/utils/index.ts index 33f8d632b..aaa9ff663 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,6 +2,7 @@ import * as Common from "shock-common"; export * from "./Date"; export { default as Http } from "./Http"; +export * from "./Error"; export const logger = { log: (...args: any[]) => console.log(...args), From c13dc687de9a4ea5aa9725c676ce4004915ac285 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 16:46:31 -0400 Subject: [PATCH 12/22] reducers readme/guidelines --- src/reducers/README.md | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/reducers/README.md diff --git a/src/reducers/README.md b/src/reducers/README.md new file mode 100644 index 000000000..89e4fafb7 --- /dev/null +++ b/src/reducers/README.md @@ -0,0 +1,50 @@ +# Reducers + +## Guidelines + +- Define an state interface, named according to the reducer, e.g. `interface NodeState { foo: string }`. +- Declare an initial state, of the state type, e.g: `const INITIAL_STATE: NodeState = { foo: 'baz' }`. +- Import `AnyAction` from `redux`, this will be the type for the `action` the reducer accepts. +- Either import the actions enum from an action creators file, e.g. `import { NODE_ACTIONS } from '../actions/NodeActions'` or use a single action creator's `.match()` method if it was created via `createAction` from `@reduxjs/toolkit`: + +```ts +import { AnyAction } from "redux"; +import { NODE_ACTIONS, setFoo } from "../actions/NodeActions"; +// ... +const node = (state: NodeState, action: ShockAction) => { + try { + if (action.type === NODE_ACTIONS.setFoo) { + // foo will NOT be typed as string + const { foo } = action.payload; + return { ...state, foo }; + } + if (setFoo.match(action)) { + // foo will be typed as string + const { foo } = action.payload; + return { ...state, foo }; + } + } catch (e) { + logger.error(`Error inside Node reducer:`); + logger.error(e); + } +}; +``` + +- Prefer `immer` over homegrown immutable data, especially for complex updates, the resulting code will be cleaner/shorter/less buggier. Small example: + +```ts +const node = produce((draft: NodeState, action: ShockAction) => { + try { + if (action.type === "setFoo") { + draft.foo = action.data.foo; + } + } catch (e) { + logger.error(`Error inside Node reducer:`); + logger.error(e); + } +}, INITIAL_STATE); +``` + +- Wrap every reducer's body in a try-catch. Inside the catch block, if the reducer can reasonably handle the error, then it should handle it, otherwise, return the current state, always log the error and pre-pend a `logger.error('Error inside X reducer:')` before logging the actual error. + +- Only one default export per file, the reducer, if something else "needs" to be exported, then it doesn't belong in a reducer file. E.g. typings should go into `shock-common`. From 07161604f7e413c7012c2f30e2d647c947a0c130 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 16:47:05 -0400 Subject: [PATCH 13/22] sagas README/guidelines --- src/store/sagas/README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/store/sagas/README.md diff --git a/src/store/sagas/README.md b/src/store/sagas/README.md new file mode 100644 index 000000000..c466f6192 --- /dev/null +++ b/src/store/sagas/README.md @@ -0,0 +1,33 @@ +# Sagas + +Sagas are preferred over thunks because: + +- No overloading of `dispatch()`. +- More expressive power. +- Called on every store tick so they can react to state changes. + +## Guidelines + +- Sagas can import from services, actions and selectors but not from reducers. +- Only one default export per file, a saga named according to the file name and scope of the code, e.g. `nodeSaga` in `node.ts`, if something else "needs" to be exported, then it doesn't belong in a saga file. E.g. in utils. That saga can then run other sagas as needed. Naming helps error traces. +- Use `getStore()` from `./_store` and not from elsewhere. +- In the future we should switch to channel events to avoid the `getStore()` conundrum. +- Ping saga handles online state. +- Build and connect sockets/polls when `Selectors.isReady(yield select())` returns true, disconnect them and tear them down when false, this simplifies token/user handling. +- Call `_setStore()` on store creation but before running the root saga. +- Wrap every single saga's body in a try-catch. Inside the catch block, if the saga can reasonably handle the error, then it should handle it, always log the error and pre-pend a `logger.error('Error inside [sagaName]* ()')` before logging the actual error. E.g.: + +```ts +function* handlePing() { + try { + // ... + } catch (e) { + logger.error("Error inside handlePing* ()"); + logger.error(e); + } +} +``` + +- Do not do conditional dispatch based on state inside the saga, let the reducer handle the action as it seems fit, reducers are pure and are more easily testable, only check for conditions inside of those. +- Use `Common.YieldReturn` for type safety. +- There's one exception to all of these rules and that is the `_store` file. From 6763357bb093a10c0882ad2e017fe37524c03cab Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 17:09:04 -0400 Subject: [PATCH 14/22] node sagas (placeholders) --- src/store/sagas/node.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/store/sagas/node.ts diff --git a/src/store/sagas/node.ts b/src/store/sagas/node.ts new file mode 100644 index 000000000..cd95cf388 --- /dev/null +++ b/src/store/sagas/node.ts @@ -0,0 +1,29 @@ +import { all, takeEvery } from "redux-saga/effects"; + +import * as Utils from "../../utils"; +import { tokenDidInvalidate } from "../../actions/NodeActions"; + +// eslint-disable-next-line require-yield +function* handleTokenInvalidation() { + try { + // Utils.navigate("/"); + } catch (e) { + Utils.logger.error(`Error inside handleTokenInvalidation* ()`); + Utils.logger.error(e); + } +} + +function* auth() { + try { + yield; + } catch (e) { + Utils.logger.error("Error inside auth* ()"); + Utils.logger.error(e); + } +} + +function* nodeSaga() { + yield all([auth, takeEvery(tokenDidInvalidate, handleTokenInvalidation)]); +} + +export default nodeSaga; From ccb78513030d8ab4ecbb3d2100e0c73997e466f6 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 17:10:50 -0400 Subject: [PATCH 15/22] ping saga --- src/store/sagas/ping.ts | 120 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/store/sagas/ping.ts diff --git a/src/store/sagas/ping.ts b/src/store/sagas/ping.ts new file mode 100644 index 000000000..5846f9c96 --- /dev/null +++ b/src/store/sagas/ping.ts @@ -0,0 +1,120 @@ +import { takeEvery, select, put } from "redux-saga/effects"; +import SocketIO from "socket.io-client"; +import { Constants } from "shock-common"; + +import * as Utils from "../../utils"; +import * as Selectors from "../selectors"; +import * as NodeActions from "../../actions/NodeActions"; + +import { getStore } from "./_store"; + +let socket: ReturnType | null = null; + +function* ping() { + try { + const { + node: { authToken: token, hostIP: host } + } = Selectors.selectStateRoot(yield select()); + + if ((!token || !host) && socket) { + Utils.logger.log( + `Will kill ping socket because of token invalidation or host was unset` + ); + socket.off("*"); + socket.close(); + socket = null; + + // force next tick + yield put({ type: "shock::keepAlive" }); + } + + if (token && host && !socket) { + Utils.logger.log(`Will try to connect ping socket`); + socket = SocketIO(`http://${host}/shockping`, { + query: { + token + } + }); + + socket.on("shockping", () => { + getStore().dispatch(NodeActions.ping(Date.now())); + }); + + socket.on(Constants.ErrorCode.NOT_AUTH, () => { + getStore().dispatch(NodeActions.tokenDidInvalidate()); + }); + + socket.on("$error", (e: string) => { + Utils.logger.error(`Error received by ping socket: ${e}`); + }); + + socket.on("connect_error", (e: unknown) => { + Utils.logger.error(`ping socket connect_error`); + Utils.logger.error(e); + }); + + socket.on("connect_timeout", (timeout: unknown) => { + Utils.logger.log(`ping socket connect_timeout`); + Utils.logger.log(timeout); + }); + + socket.on("connect", () => { + Utils.logger.log("ping socket connect"); + }); + + socket.on("disconnect", (reason: string) => { + Utils.logger.log(`ping socket disconnected due to -> ${reason}`); + + // from docs + if (reason === "io server disconnect") { + // the disconnection was initiated by the server, you need to reconnect manually + socket && socket.connect(); + } + // else the socket will automatically try to reconnect + }); + + socket.on("error", (e: unknown) => { + Utils.logger.error(`Error inside ping socket`); + Utils.logger.error(e); + }); + + socket.on("reconnect", (attemptNumber: number) => { + Utils.logger.log(`ping socket reconnect attempt -> ${attemptNumber}`); + }); + + socket.on("reconnecting", (attemptNumber: number) => { + Utils.logger.log( + `ping socket reconnecting attempt -> ${attemptNumber}` + ); + }); + + socket.on("reconnect_error", (e: unknown) => { + Utils.logger.log(`ping socket reconnect_error`); + Utils.logger.error(e); + }); + + socket.on("reconnect_failed", () => { + Utils.logger.error(`ping socket reconnect_failed`); + }); + + socket.on("ping", () => { + Utils.logger.log(`ping socket pinging api (socket.io internal)`); + }); + + socket.on("pong", () => { + Utils.logger.log(`ping socket ponged by api (socket.io internal)`); + + getStore().dispatch(NodeActions.ping(Date.now())); + }); + } + } catch (err) { + Utils.logger.error("Error inside ping* ()"); + Utils.logger.error(err.message); + } +} + +function* rootSaga() { + yield takeEvery("*", ping); +} + +export default rootSaga; From f4171a9dc7a7ee1c1cc738bdcbf757efef9d47ca Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 17:11:32 -0400 Subject: [PATCH 16/22] sagas module --- src/store/sagas/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/store/sagas/index.ts diff --git a/src/store/sagas/index.ts b/src/store/sagas/index.ts new file mode 100644 index 000000000..96659280c --- /dev/null +++ b/src/store/sagas/index.ts @@ -0,0 +1,13 @@ +import { all } from "redux-saga/effects"; + +import node from "./node"; +import ping from "./ping"; + +import { _setStore } from "./_store"; + +function* rootSaga() { + yield all([node, ping]); +} + +export { _setStore }; +export default rootSaga; From 3d0230a00cf714028cf7955ae9da073fd16df9a9 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 17:13:48 -0400 Subject: [PATCH 17/22] integrate sagas module into store --- src/store/index.ts | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/store/index.ts b/src/store/index.ts index de6c2b01c..3d95fbcd2 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,4 +1,4 @@ -import { createStore, applyMiddleware, compose } from "redux"; +import { createStore, applyMiddleware, compose, AnyAction } from "redux"; import thunk from "redux-thunk"; import rootReducer, { State } from "../reducers"; import { useSelector as origUseSelector } from "react-redux"; @@ -7,6 +7,9 @@ import storage from "redux-persist/lib/storage"; // defaults to localStorage for import Migrations from "./Migrations"; import createMigrate from "redux-persist/es/createMigrate"; import autoMergeLevel2 from "redux-persist/es/stateReconciler/autoMergeLevel2"; +import createSagaMiddleware from "redux-saga"; + +import rootSaga, { _setStore as setSagaStore } from "./sagas"; const persistConfig = { key: "root", @@ -19,9 +22,13 @@ const persistConfig = { }) }; -const persistedReducer = persistReducer(persistConfig, rootReducer); +const persistedReducer = persistReducer( + persistConfig, + rootReducer +); const initializeStore = () => { + const sagaMiddleware = createSagaMiddleware(); // @ts-expect-error const store = window.__REDUX_DEVTOOLS_EXTENSION__ ? createStore( @@ -29,8 +36,22 @@ const initializeStore = () => { // @ts-expect-error compose(applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__()) ) - : createStore(persistedReducer, applyMiddleware(thunk)); + : createStore(persistedReducer, applyMiddleware(thunk, sagaMiddleware)); let persistor = persistStore(store); + setSagaStore(store); + sagaMiddleware.run(rootSaga); + // In the future if polls (which cause ticks in the store) are moved to sagas + // they will be dependant on the ping socket, we need a keep alive tick for + // when the are no actions being dispatched making the store tick and + // therefore the ping saga realizing the socket died, if it did so. Ideally, + // the ping/socket subscription should emit a timeout event of such but I'd + // rather do that when I learn Event Channels and implement the ping socket + // using that. + setInterval(() => { + store.dispatch({ + type: "shock::keepAlive" + }); + }, 20000); return { store, persistor }; }; From ac7bde14c70b50de3dde42207e2bdee5b05d25d2 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 18:02:06 -0400 Subject: [PATCH 18/22] remove invalid references --- src/store/sagas/node.ts | 6 ++++-- src/store/sagas/ping.ts | 22 ++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/store/sagas/node.ts b/src/store/sagas/node.ts index cd95cf388..0c7767e6c 100644 --- a/src/store/sagas/node.ts +++ b/src/store/sagas/node.ts @@ -1,7 +1,6 @@ import { all, takeEvery } from "redux-saga/effects"; import * as Utils from "../../utils"; -import { tokenDidInvalidate } from "../../actions/NodeActions"; // eslint-disable-next-line require-yield function* handleTokenInvalidation() { @@ -23,7 +22,10 @@ function* auth() { } function* nodeSaga() { - yield all([auth, takeEvery(tokenDidInvalidate, handleTokenInvalidation)]); + yield all([ + auth, + takeEvery("node/tokenDidInvalidate", handleTokenInvalidation) + ]); } export default nodeSaga; diff --git a/src/store/sagas/ping.ts b/src/store/sagas/ping.ts index 5846f9c96..523790ba4 100644 --- a/src/store/sagas/ping.ts +++ b/src/store/sagas/ping.ts @@ -3,8 +3,6 @@ import SocketIO from "socket.io-client"; import { Constants } from "shock-common"; import * as Utils from "../../utils"; -import * as Selectors from "../selectors"; -import * as NodeActions from "../../actions/NodeActions"; import { getStore } from "./_store"; @@ -14,7 +12,7 @@ function* ping() { try { const { node: { authToken: token, hostIP: host } - } = Selectors.selectStateRoot(yield select()); + } = yield select(); if ((!token || !host) && socket) { Utils.logger.log( @@ -37,11 +35,18 @@ function* ping() { }); socket.on("shockping", () => { - getStore().dispatch(NodeActions.ping(Date.now())); + getStore().dispatch({ + type: "node/ping", + payload: { + timestamp: Utils.normalizeTimestampToMs(Date.now()) + } + }); }); socket.on(Constants.ErrorCode.NOT_AUTH, () => { - getStore().dispatch(NodeActions.tokenDidInvalidate()); + getStore().dispatch({ + type: "node/tokenDidInvalidate" + }); }); socket.on("$error", (e: string) => { @@ -104,7 +109,12 @@ function* ping() { socket.on("pong", () => { Utils.logger.log(`ping socket ponged by api (socket.io internal)`); - getStore().dispatch(NodeActions.ping(Date.now())); + getStore().dispatch({ + type: "ping", + payload: { + timestamp: Utils.normalizeTimestampToMs(Date.now()) + } + }); }); } } catch (err) { From 17fd7ab88e7aded0d15b968b03d3352312f68886 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 18:10:37 -0400 Subject: [PATCH 19/22] fix several misc issues with Feed --- src/pages/Feed/index.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/pages/Feed/index.js b/src/pages/Feed/index.js index 7bae95383..422e15b18 100644 --- a/src/pages/Feed/index.js +++ b/src/pages/Feed/index.js @@ -7,7 +7,8 @@ import React, { useMemo, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; +import * as Common from "shock-common"; import { processDisplayName } from "../../utils/String"; import { attachMedia } from "../../utils/Torrents"; @@ -16,7 +17,6 @@ import BottomBar from "../../common/BottomBar"; import UserIcon from "./components/UserIcon"; import SendTipModal from "./components/SendTipModal"; import Loader from "../../common/Loader"; -import ShockAvatar from "../../common/ShockAvatar"; import { subscribeFollows } from "../../actions/FeedActions"; @@ -28,9 +28,9 @@ const SharedPost = React.lazy(() => import("../../common/Post/SharedPost")); const FeedPage = () => { const dispatch = useDispatch(); - const follows = useSelector(({ feed }) => feed.follows); - const posts = useSelector(({ feed }) => feed.posts); - const userProfiles = useSelector(({ userProfiles }) => userProfiles); + const follows = Store.useSelector(({ feed }) => feed.follows); + const posts = Store.useSelector(({ feed }) => feed.posts); + const userProfiles = Store.useSelector(({ userProfiles }) => userProfiles); const [tipModalData, setTipModalOpen] = useState(null); const [unlockModalData, setUnlockModalOpen] = useState(null); const { avatar } = Store.useSelector(Store.selectSelfUser); @@ -77,14 +77,8 @@ const FeedPage = () => { }, [dispatch]); useEffect(() => { - const subscription = startFollowsSubscription(); - - return async () => { - const resolvedSubscription = await subscription; - resolvedSubscription.off("*"); - resolvedSubscription.close(); - }; - }, [dispatch]); + startFollowsSubscription(); + }, [dispatch, startFollowsSubscription]); useLayoutEffect(() => { attachMedia( @@ -106,11 +100,15 @@ const FeedPage = () => {
{follows?.map(follow => { const publicKey = follow.user; - const profile = userProfiles[publicKey] ?? {}; + const profile = + userProfiles[publicKey] ?? Common.createEmptyUser(publicKey); return ( ); })} @@ -162,6 +160,8 @@ const FeedPage = () => { openUnlockModal={toggleUnlockModal} // TODO: User online status handling isOnlineNode + tipCounter={undefined} + tipValue={undefined} /> ); From 7edb5e2fc7bac8628ebf7f7864cff78d249a3cb4 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 18:10:47 -0400 Subject: [PATCH 20/22] ignore this error for now --- src/utils/index.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index aaa9ff663..198f005e3 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -136,10 +136,13 @@ export const retryOperation = ( .then(resolve) .catch(reason => { if (retries > 0) { - return wait(delay) - .then(retryOperation.bind(null, operation, delay, retries - 1)) - .then(resolve) - .catch(reject); + return ( + wait(delay) + .then(retryOperation.bind(null, operation, delay, retries - 1)) + // @ts-expect-error + .then(resolve) + .catch(reject) + ); } return reject(reason); }); From 2fcc05c9861497da6ddfa43b4a499b9f55548921 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 18:11:38 -0400 Subject: [PATCH 21/22] ignore these errors for now --- src/actions/NodeActions.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/actions/NodeActions.js b/src/actions/NodeActions.js index fd5bbbd13..e26b8ae7f 100644 --- a/src/actions/NodeActions.js +++ b/src/actions/NodeActions.js @@ -163,6 +163,7 @@ export const unlockWallet = ({ alias, password }) => async dispatch => { alias: data.user.alias, authToken: data.authorization, publicKey: data.user.publicKey, + // @ts-expect-error authTokenExpirationDate: decodedToken.exp } }); @@ -188,6 +189,7 @@ export const createAlias = ({ alias, password }) => async dispatch => { alias: data.user.alias, authToken: data.authorization, publicKey: data.user.publicKey, + // @ts-expect-error authTokenExpirationDate: decodedToken.exp } }); @@ -220,6 +222,7 @@ export const createWallet = ({ alias, password }) => async dispatch => { alias: data.user.alias, authToken: data.authorization, publicKey: data.user.publicKey, + // @ts-expect-error authTokenExpirationDate: decodedToken.exp } }); From f9d77d68ab1ed1078331db0fd518eeb5c1fad55a Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Thu, 1 Apr 2021 18:22:17 -0400 Subject: [PATCH 22/22] ensure saga middleware gets applied --- src/store/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/store/index.ts b/src/store/index.ts index 3d95fbcd2..d00e4415c 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -29,14 +29,15 @@ const persistedReducer = persistReducer( const initializeStore = () => { const sagaMiddleware = createSagaMiddleware(); + const appliedMiddleware = applyMiddleware(thunk, sagaMiddleware); // @ts-expect-error const store = window.__REDUX_DEVTOOLS_EXTENSION__ ? createStore( persistedReducer, // @ts-expect-error - compose(applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__()) + compose(appliedMiddleware, window.__REDUX_DEVTOOLS_EXTENSION__()) ) - : createStore(persistedReducer, applyMiddleware(thunk, sagaMiddleware)); + : createStore(persistedReducer, appliedMiddleware); let persistor = persistStore(store); setSagaStore(store); sagaMiddleware.run(rootSaga);