Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(messenger): redux and weshnet connection #740

Merged
merged 11 commits into from
Nov 23, 2023
5 changes: 5 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ module.exports = {
selector:
"MemberExpression[object.name='StyleSheet'][property.name='create']",
},
{
message:
"Do not use JSON.parse, it breaks type safety, use sanitization utils instead",
selector: "MemberExpression[object.name='JSON'][property.name='parse']",
},
],
},
overrides: [
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,4 @@ networks.json: node_modules validate-networks
.PHONY: unused-exports
unused-exports: node_modules
## TODO unexclude all paths except packages/api;packages/contracts-clients;packages/evm-contracts-clients
npx ts-unused-exports ./tsconfig.json --excludePathsFromReport="packages/api;packages/contracts-clients;packages/evm-contracts-clients;packages/components/socialFeed/RichText/inline-toolbar;./App.tsx;.*\.web|.electron|.d.ts" --ignoreTestFiles
npx ts-unused-exports ./tsconfig.json --excludePathsFromReport="packages/weshnet;packages/store/slices/message.ts;packages/utils/types/message.ts;packages/api;packages/contracts-clients;packages/evm-contracts-clients;packages/components/socialFeed/RichText/inline-toolbar;./App.tsx;.*\.web|.electron|.d.ts" --ignoreTestFiles
2 changes: 2 additions & 0 deletions packages/components/multisig/MultisigTransactionActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export const MultisigTransactionActions: React.FC<
multisigAddress,
},
currentSignatures: signatures,
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
pubkey: JSON.parse(multisigPubkeyJson),
transactionId: id,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useNSUserInfo } from "../../../../hooks/useNSUserInfo";
import { useSelectedNetworkInfo } from "../../../../hooks/useSelectedNetwork";
import { getNetworkObjectId, parseUserId } from "../../../../networks";
import { useAppNavigation } from "../../../../utils/navigation";
import { safeJSONParse, zodTryParse } from "../../../../utils/sanitize";
import { safeParseJSON, zodTryParse } from "../../../../utils/sanitize";
import {
neutral00,
neutral33,
Expand Down Expand Up @@ -64,7 +64,7 @@ export const SocialArticleCard: FC<{
const articleCardHeight = windowWidth < SOCIAL_FEED_BREAKPOINT_M ? 214 : 254;
const thumbnailImageWidth = viewWidth / 3;

const postMetadata = safeJSONParse(localPost.metadata);
const postMetadata = safeParseJSON(localPost.metadata);
const metadata = zodTryParse(ZodSocialFeedArticleMetadata, postMetadata);
const oldMetadata = zodTryParse(ZodSocialFeedPostMetadata, postMetadata);
const thumbnailImage =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export const SocialCommentCard: React.FC<SocialCommentCardProps> = ({
[data],
);
const moreCommentsCount = localComment.subPostLength - comments.length;
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
const metadata = JSON.parse(localComment.metadata);
const authorNSInfo = useNSUserInfo(localComment.authorId);
const username = authorNSInfo?.metadata?.tokenId
Expand Down
2 changes: 2 additions & 0 deletions packages/hooks/multisig/useMultisigTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export const useMultisigTransactions = (
const t: ParsedTransaction = {
...tx,
msgs,
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
fee: JSON.parse(tx.feeJson),
createdAt: new Date(tx.createdAt),
};
Expand Down
4 changes: 4 additions & 0 deletions packages/screens/RiotersFooter/RiotersFooterScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ export const RiotersFooterScreen: React.FC = () => {
imageUri: responseJSON.image.startsWith("ipfs://")
? ipfsURLToHTTPURL(responseJSON.image)
: responseJSON.image,
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
borderRadius: JSON.parse(nft.additional).borderRadius || 0,
});
}
Expand Down Expand Up @@ -295,6 +297,8 @@ export const RiotersFooterScreen: React.FC = () => {

const onReceiveDragDrop = useCallback(
(event: any) => {
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
const nft: NFT = JSON.parse(event.dragged.payload);
if (
!nftDroped ||
Expand Down
2 changes: 1 addition & 1 deletion packages/screens/Settings/SettingsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { ScreenFC, useAppNavigation } from "../../utils/navigation";
import { neutralA3, primaryColor } from "../../utils/style/colors";
import { fontSemibold14 } from "../../utils/style/fonts";
import { modalMarginPadding } from "../../utils/style/modals";
import { createWeshClient } from "../../utils/weshnet";
import { createWeshClient } from "../../weshnet";

const NFTAPIKeyInput: React.FC = () => {
const userIPFSKey = useSelector(selectNFTStorageAPI);
Expand Down
4 changes: 4 additions & 0 deletions packages/scripts/burnTotal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const main = async () => {
const statusCmd = `teritorid status --node '${endpoint}'`;
const res = child_process.spawnSync(statusCmd, { shell: true });
const statusJSON = res.stderr.toString();
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
const status = JSON.parse(statusJSON);
const latestBlockHeight: string = status.SyncInfo.latest_block_height; // FIXME: sanitize

Expand All @@ -45,6 +47,8 @@ const main = async () => {
while (true) {
const cmd = `teritorid query txs --height ${latestBlockHeight} --events message.action=/teritori.mint.v1beta1.MsgBurnTokens --node '${endpoint}' --limit ${batchSize} --page ${page} -o json`;
const burnTxsJSON = child_process.execSync(cmd).toString();
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
const txs = JSON.parse(burnTxsJSON);

for (const tx of txs.txs) {
Expand Down
260 changes: 260 additions & 0 deletions packages/store/slices/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
import {
createEntityAdapter,
createSlice,
EntityState,
PayloadAction,
} from "@reduxjs/toolkit";
import { uniqBy } from "lodash";
import moment from "moment";

import {
ContactRequest,
Conversation,
Message,
CONVERSATION_TYPES,
PeerItem,
} from "./../../utils/types/message";
import { weshConfig } from "../../weshnet/config";
import { stringFromBytes } from "../../weshnet/utils";
import { RootState } from "../store";

const contactRequestEntityAdapter = createEntityAdapter<ContactRequest>();
const contactRequestSelectors = contactRequestEntityAdapter.getSelectors();

const peerEntityAdapter = createEntityAdapter<PeerItem>();
const peerSelectors = peerEntityAdapter.getSelectors();

const conversationEntityAdapter = createEntityAdapter<Conversation>();
const conversationSelectors = conversationEntityAdapter.getSelectors();

interface KVItem<T> {
id: string;
value: T;
}
const kvEntityAdapter = createEntityAdapter<KVItem<string>>();
const kvSelectors = kvEntityAdapter.getSelectors();

const messageEntityAdapter = createEntityAdapter<Message>({
sortComparer: (a, b) =>
moment(b.timestamp).valueOf() - moment(a.timestamp).valueOf(),
});
const messageSelectors = messageEntityAdapter.getSelectors();

const groupEntityAdapter = createEntityAdapter<KVItem<EntityState<Message>>>();
const groupSelectors = groupEntityAdapter.getSelectors();

export interface MessageState {
isWeshConnected: boolean;
peers: EntityState<PeerItem>;
contactInfo: {
name: string;
avatar: string;
publicRendezvousSeed: string;
shareLink: string;
};
contactRequests: EntityState<ContactRequest>;
messages: EntityState<KVItem<EntityState<Message>>>;
conversations: EntityState<Conversation>;
lastIds: EntityState<KVItem<string>>;
}

const initialState: MessageState = {
isWeshConnected: false,
contactInfo: {
name: "Anon",
avatar: "",
publicRendezvousSeed: "",
shareLink: "",
},
messages: groupEntityAdapter.getInitialState(),
contactRequests: contactRequestEntityAdapter.getInitialState(),
conversations: conversationEntityAdapter.getInitialState(),
lastIds: kvEntityAdapter.getInitialState(),
peers: peerEntityAdapter.getInitialState(),
};

export const selectIsWeshConnected = (state: RootState) =>
state.message.isWeshConnected;

export const selectContactInfo = (state: RootState) =>
state.message.contactInfo;

export const selectGroup = (state: RootState, groupPk: string) =>
groupSelectors.selectById(state.message.messages, groupPk)?.value;

export const selectMessageList = (state: RootState, groupPk: string) => {
const group = selectGroup(state, groupPk);
if (!group) return [];
return messageSelectors.selectAll(group);
};

export const selectPeerList = (state: RootState) =>
peerSelectors.selectAll(state.message.peers);

export const selectPeerById = (state: RootState, id: string) =>
peerSelectors.selectById(state.message.peers, id);

export const selectLastIdByKey = (state: RootState, key: string) =>
kvSelectors.selectById(state.message.lastIds, key)?.value;

export const selectLastMessageByGroupPk = (
state: RootState,
groupPk: string,
) => {
const messages = selectMessageList(state, groupPk);
if (!messages.length) return undefined;
return messages[0];
};

// TODO: optimize
export const selectLastContactMessageByGroupPk = (
state: RootState,
groupPk: string,
) => {
const messages = selectMessageList(state, groupPk);
const filtered = messages.filter(
(item) => item.senderId !== stringFromBytes(weshConfig?.config?.accountPk),
);
if (!filtered.length) return undefined;
return filtered[0];
};

export const selectContactRequestList = (state: RootState) =>
contactRequestSelectors.selectAll(state.message.contactRequests);

export const selectConversationList = (
state: RootState,
conversationType: CONVERSATION_TYPES = CONVERSATION_TYPES.ACTIVE,
) => {
const conversations = conversationSelectors.selectAll(
state.message.conversations,
);
switch (conversationType) {
case CONVERSATION_TYPES.ALL: {
return conversations;
}
case CONVERSATION_TYPES.ARCHIVED: {
return conversations.filter((conv) => conv.status === "archived");
}
case CONVERSATION_TYPES.ACTIVE:
default:
return conversations.filter((conv) => conv.status === "active");
}
};

export const selectConversationById = (state: RootState, id: string) =>
conversationSelectors.selectById(state.message.conversations, id);

const messageSlice = createSlice({
name: "message",
initialState,
reducers: {
setIsWeshConnected: (state, action: PayloadAction<boolean>) => {
state.isWeshConnected = action.payload;
},
setMessage: (
state,
action: PayloadAction<{ groupPk: string; data: Message }>,
) => {
let group = groupSelectors.selectById(
state.messages,
action.payload.groupPk,
);
if (!group) {
group = {
id: action.payload.groupPk,
value: messageEntityAdapter.getInitialState(),
};
group.value = messageEntityAdapter.setOne(
group.value,
action.payload.data,
);
groupEntityAdapter.setOne(state.messages, group);
} else {
groupEntityAdapter.updateOne(state.messages, {
id: action.payload.groupPk,
changes: {
value: messageEntityAdapter.setOne(
group.value,
action.payload.data,
),
},
});
}
},
setPeerList: (state, action: PayloadAction<PeerItem[]>) => {
peerEntityAdapter.setAll(state.peers, action.payload);
},
updateMessageReactions: (
state,
action: PayloadAction<{ groupPk: string; data: Message }>,
) => {
if (action.payload.data.parentId) {
const group = groupSelectors.selectById(
state.messages,
action.payload.groupPk,
);
if (!group) return;
const message = messageSelectors.selectById(
group.value,
action.payload.data.parentId,
);
if (!message) return;
messageEntityAdapter.updateOne(group.value, {
id: action.payload.data?.parentId,
changes: {
reactions: uniqBy(
[...(message.reactions || []), action.payload.data], // TODO: normalize
"id",
),
},
});
}
},
setContactRequestList: (state, action: PayloadAction<ContactRequest[]>) => {
contactRequestEntityAdapter.setAll(state.contactRequests, action.payload);
},
setContactRequest: (state, action: PayloadAction<ContactRequest>) => {
contactRequestEntityAdapter.setOne(state.contactRequests, action.payload);
},
setConversationList: (state, action: PayloadAction<Conversation>) => {
conversationEntityAdapter.setOne(state.conversations, action.payload);
},
updateConversationById: (
state,
action: PayloadAction<Partial<Conversation>>,
) => {
if (action.payload.id) {
conversationEntityAdapter.updateOne(state.conversations, {
id: action.payload.id,
changes: action.payload,
});
}
},
setLastId: (state, action: PayloadAction<KVItem<string>>) => {
kvEntityAdapter.setOne(state.lastIds, action.payload);
},

setContactInfo: (
state,
action: PayloadAction<Partial<MessageState["contactInfo"]>>,
) => {
state.contactInfo = { ...state.contactInfo, ...action.payload };
},
},
});

export const {
setMessage,
setContactRequestList,
setContactRequest,
setConversationList,
updateMessageReactions,
setLastId,
setContactInfo,
updateConversationById,
setPeerList,
setIsWeshConnected,
} = messageSlice.actions;

export const messageReducer = messageSlice.reducer;
2 changes: 2 additions & 0 deletions packages/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
marketplaceFilters,
marketplaceFilterUI,
} from "./slices/marketplaceFilters";
import { messageReducer } from "./slices/message";
import { searchReducer } from "./slices/search";
import {
multisigTokensAdapter,
Expand Down Expand Up @@ -79,6 +80,7 @@ const rootReducer = combineReducers({
marketplaceFilters,
marketplaceFilterUI,
search: searchReducer,
message: messageReducer,
});

const persistedReducer = persistReducer(persistConfig, rootReducer);
Expand Down
4 changes: 4 additions & 0 deletions packages/utils/gno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,12 @@ export const extractGnoNumber = (str: string) => {
};
export const extractGnoString = (str: string) => {
const jsonStr = str.slice(str.indexOf(`"`), str.lastIndexOf(`"`) + 1);
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
return JSON.parse(jsonStr) as string;
};
export const extractGnoJSONString = (str: string) => {
// FIXME: sanitize
// eslint-disable-next-line no-restricted-syntax
return JSON.parse(extractGnoString(str));
};
Loading
Loading