From 28a853813e5d48011ae3bd01c7761f5759d30e2a Mon Sep 17 00:00:00 2001 From: Monalisha Mishra Date: Tue, 19 Mar 2024 13:01:44 +0530 Subject: [PATCH] added --- package.json | 4 +- src/App.tsx | 108 +-- src/assets/chat/unlock.svg | 9 + .../chat/unlockProfile/UnlockProfile.tsx | 68 ++ .../chat/w2wChat/searchBar/SearchBar.tsx | 94 +-- src/contexts/AppContext.tsx | 751 +++++++++--------- src/hooks/useModalBlur.tsx | 5 +- src/index.css | 2 +- src/modules/chat/ChatModule.tsx | 59 +- src/sections/chat/ChatSidebarSection.tsx | 131 +-- yarn.lock | 20 +- 11 files changed, 676 insertions(+), 575 deletions(-) create mode 100644 src/assets/chat/unlock.svg create mode 100644 src/components/chat/unlockProfile/UnlockProfile.tsx diff --git a/package.json b/package.json index 5456140d0e..19751b7f8f 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,9 @@ "@mui/icons-material": "^5.8.4", "@mui/lab": "^5.0.0-alpha.72", "@mui/material": "^5.5.0", - "@pushprotocol/restapi": "1.6.12-alpha.1", + "@pushprotocol/restapi": "1.7.2", "@pushprotocol/socket": "0.5.3", - "@pushprotocol/uiweb": "1.3.1-alpha.8", + "@pushprotocol/uiweb": "1.2.7", "@reduxjs/toolkit": "^1.7.1", "@testing-library/dom": "^9.0.1", "@testing-library/jest-dom": "^4.2.4", diff --git a/src/App.tsx b/src/App.tsx index ef7cd19bc5..67b4050e28 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -44,7 +44,8 @@ import { ISpaceFeedProps, ISpaceInvitesProps, ISpaceWidgetProps, - SpacesUI, SpacesUIProvider + SpacesUI, + SpacesUIProvider, } from '@pushprotocol/uiweb'; import { useUpdateTheme } from '@web3-onboard/react'; import { darkTheme, lightTheme } from 'config/spaceTheme'; @@ -72,15 +73,15 @@ export interface IUseSpaceReturnValues { // Extend the console const extendConsole = () => { - "use strict"; + 'use strict'; try { var disabledConsoles = {}; console.enable = function (level, enabled) { - if (window.console === "undefined" || !window.console || window.console === null) { + if (window.console === 'undefined' || !window.console || window.console === null) { window.console = {}; } - if (window.console[level] === "undefined" || !window.console[level] || window.console[level] === null) { - window.console[level] = function () { }; + if (window.console[level] === 'undefined' || !window.console[level] || window.console[level] === null) { + window.console[level] = function () {}; } if (enabled) { if (disabledConsoles[level]) { @@ -88,31 +89,31 @@ const extendConsole = () => { } } else { disabledConsoles[level] = window.console[level]; - window.console[level] = function () { }; + window.console[level] = function () {}; } }; } catch (e) { - console.error("Extended console() threw an error!"); + console.error('Extended console() threw an error!'); console.debug(e); } -} +}; // extend console extendConsole(); -// Disable console but not on localhost -if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1") { - if (appConfig?.appEnv === "prod") { - console.enable("debug", false); - console.enable("log", false); - console.enable("info", false); - - // disable console.warn in prod - if (appConfig?.appEnv === "prod") { - console.enable("warn", false); - } - } -} +// // Disable console but not on localhost +// if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1") { +// if (appConfig?.appEnv === "prod") { +// console.enable("debug", false); +// console.enable("log", false); +// console.enable("info", false); + +// // disable console.warn in prod +// if (appConfig?.appEnv === "prod") { +// console.enable("warn", false); +// } +// } +// } // Provess App export default function App() { @@ -154,7 +155,6 @@ export default function App() { dispatch(resetUserSlice()); }, [account]); - // console.log(isActive, chainId, account); // handle logic to reconnect in response to certain events from the provider const { allowedChain } = useInactiveListener(); @@ -186,9 +186,8 @@ export default function App() { const SidebarCollapsable = localStorage.getItem('SidebarCollapsed'); if (SidebarCollapsable) { const isSidebarCollapsed = JSON.parse(SidebarCollapsable); - setSidebarCollapsed(isSidebarCollapsed) + setSidebarCollapsed(isSidebarCollapsed); } - }, []); React.useEffect(() => { @@ -202,17 +201,17 @@ export default function App() { React.useEffect(() => { window?.Olvy?.init({ - organisation: "epns", - target: "#olvy-target", - type: "sidebar", + organisation: 'epns', + target: '#olvy-target', + type: 'sidebar', view: { showSearch: false, compact: false, showHeader: true, // only applies when widget type is embed. you cannot hide header for modal and sidebar widgets showUnreadIndicator: true, - unreadIndicatorColor: "#cc1919", - unreadIndicatorPosition: "top-right" - } + unreadIndicatorColor: '#cc1919', + unreadIndicatorPosition: 'top-right', + }, }); return function cleanup() { window?.Olvy?.teardown(); @@ -244,20 +243,22 @@ export default function App() { const librarySigner = provider?.getSigner(account); - const spaceUI = useMemo(() => new SpacesUI({ - account: account, - signer: librarySigner, - pgpPrivateKey: pgpPvtKey, - env: appConfig?.appEnv, - }), [account, librarySigner, pgpPvtKey, appConfig?.appEnv]); - + const spaceUI = useMemo( + () => + new SpacesUI({ + account: account, + signer: librarySigner, + pgpPrivateKey: pgpPvtKey, + env: appConfig?.appEnv, + }), + [account, librarySigner, pgpPvtKey, appConfig?.appEnv] + ); // const { spaceUI } = useSpaceComponents(); const location = useLocation(); const isSnapPage = location?.pathname.includes('/snap'); - return ( {/* {(!isActive || !allowedChain) && ( @@ -308,18 +309,31 @@ export default function App() { - {!isSnapPage && - + {!isSnapPage && ( + - } + )} - + {/* Shared among all pages, load universal things here */} - + @@ -328,13 +342,7 @@ export default function App() { - - - - - - ); } diff --git a/src/assets/chat/unlock.svg b/src/assets/chat/unlock.svg new file mode 100644 index 0000000000..dae0876e1f --- /dev/null +++ b/src/assets/chat/unlock.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/chat/unlockProfile/UnlockProfile.tsx b/src/components/chat/unlockProfile/UnlockProfile.tsx new file mode 100644 index 0000000000..860a6b7d81 --- /dev/null +++ b/src/components/chat/unlockProfile/UnlockProfile.tsx @@ -0,0 +1,68 @@ +import React, { useContext } from 'react'; +import styled, { ThemeProvider, useTheme } from 'styled-components'; + +import { ButtonV2, ImageV2, ItemVV2, SpanV2 } from 'components/reusables/SharedStylingV2'; +import { AppContext } from 'contexts/AppContext'; +import UnlockLogo from '../../../assets/chat/unlock.svg'; + +const UnlockProfile = () => { + const theme = useTheme(); + const { handleConnectWallet } = useContext(AppContext); + return ( + + + + + + + Unlock Your Push Profile + + + You need to decrypt your Push Profile to proceed. Please sign using your wallet to continue. + + + await handleConnectWallet()} + > + Unlock Profile + + + + + ); +}; + +const ModalContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + border-radius: 24px; + width: 358px; + min-height: -webkit-fill-available; + background: ${(props) => props.theme.default.bg}; + align-items: flex-start; + overflow: hidden; +`; + +export default UnlockProfile; diff --git a/src/components/chat/w2wChat/searchBar/SearchBar.tsx b/src/components/chat/w2wChat/searchBar/SearchBar.tsx index ce82a177b0..8f52fe5c5b 100644 --- a/src/components/chat/w2wChat/searchBar/SearchBar.tsx +++ b/src/components/chat/w2wChat/searchBar/SearchBar.tsx @@ -15,24 +15,15 @@ import { Context } from 'modules/chat/ChatModule'; import { AppContext } from 'types/chat'; import ArrowLeft from '../../../../assets/chat/arrowleft.svg'; - const SearchBar = ({ autofilled, searchedUser, setSearchedUser }) => { // get theme const theme = useTheme(); - const { - setHasUserBeenSearched, - activeTab, - setActiveTab, - userShouldBeSearched, - setUserShouldBeSearched, - }: AppContext = useContext(Context); + const { setHasUserBeenSearched, activeTab, setActiveTab, userShouldBeSearched, setUserShouldBeSearched }: AppContext = + useContext(Context); const [isLoadingSearch, setIsLoadingSearch] = useState(false); - - - useEffect(() => { if (searchedUser !== '' && userShouldBeSearched) { setSearchedUser(searchedUser); @@ -43,7 +34,6 @@ const SearchBar = ({ autofilled, searchedUser, setSearchedUser }) => { useEffect(() => { if (autofilled && !userShouldBeSearched) { - if (autofilled.includes('chatid')) { setSearchedUser(autofilled.split(':')[1]); } else { @@ -53,7 +43,6 @@ const SearchBar = ({ autofilled, searchedUser, setSearchedUser }) => { } }, [userShouldBeSearched, autofilled]); - const onChangeSearchBox = async (event: React.ChangeEvent): Promise => { let searchAddress = event.target.value; @@ -65,11 +54,9 @@ const SearchBar = ({ autofilled, searchedUser, setSearchedUser }) => { }; const submitSearch = (): void => { - setActiveTab(3); }; - const clearInput = (): void => { setSearchedUser(''); setHasUserBeenSearched(false); @@ -87,7 +74,7 @@ const SearchBar = ({ autofilled, searchedUser, setSearchedUser }) => { justifyContent="flex-start" width="100%" flex="initial" - margin="20px 0px 0px 0px" + margin="20px 0px 12px 0px" padding="0px 0px 14px 0px" style={{ borderBottom: '2px solid #D53893' }} > @@ -118,50 +105,48 @@ const SearchBar = ({ autofilled, searchedUser, setSearchedUser }) => { alignItems="stretch" display={activeTab == 4 ? 'none' : 'flex'} > - - e.key === 'Enter'? submitSearch():null} - value={searchedUser} - onChange={onChangeSearchBox} - placeholder="Search Web3 domain or 0x123..." - /> - {searchedUser.length > 0 && ( - - - - )} + (e.key === 'Enter' ? submitSearch() : null)} + value={searchedUser} + onChange={onChangeSearchBox} + placeholder="Search Web3 domain or 0x123..." + /> + {searchedUser.length > 0 && ( - {isLoadingSearch && ( - - )} - {!isLoadingSearch && ( - - )} + + )} + + {isLoadingSearch && ( + + )} + {!isLoadingSearch && ( + + )} + {activeTab !== 3 && activeTab !== 4 && ( @@ -182,7 +167,6 @@ const SearchBar = ({ autofilled, searchedUser, setSearchedUser }) => { borderRadius="50%" onClick={() => setActiveTab(3)} > - @@ -192,8 +176,6 @@ const SearchBar = ({ autofilled, searchedUser, setSearchedUser }) => { ); }; - - const Input = styled.input` box-sizing: border-box; display: flex; @@ -201,7 +183,7 @@ const Input = styled.input` width: 100%; height: 48px; padding: 13px 60px 13px 21px; - margin: 10px 0px 17px 0px; + margin: 10px 0px 10px 0px; border-radius: 99px; border: 1px solid transparent !important; background-color: ${(props) => props.theme.chat.snapFocusBg}; diff --git a/src/contexts/AppContext.tsx b/src/contexts/AppContext.tsx index 436796a28f..bd4e9ca5f1 100644 --- a/src/contexts/AppContext.tsx +++ b/src/contexts/AppContext.tsx @@ -1,393 +1,406 @@ // React + Web3 Essentials import { ProgressHookType, PushAPI } from '@pushprotocol/restapi'; -import { ethers } from "ethers"; -import useModalBlur from "hooks/useModalBlur"; -import React, { createContext, useContext, useEffect, useState } from "react"; +import { ethers } from 'ethers'; +import useModalBlur from 'hooks/useModalBlur'; +import React, { createContext, useContext, useEffect, useState } from 'react'; // Internal Components -import { LOADER_SPINNER_TYPE } from "components/reusables/loaders/LoaderSpinner"; -import { appConfig } from "config"; +import { LOADER_SPINNER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; +import { appConfig } from 'config'; import * as w2wHelper from 'helpers/w2w'; -import { useAccount } from "hooks"; -import useToast from "hooks/useToast"; -import { MdError } from "react-icons/md"; -import { useDispatch, useSelector } from "react-redux"; -import { setUserPushSDKInstance } from "redux/slices/userSlice"; -import { ConnectedUser } from "types/chat"; -import { AppContextType, BlockedLoadingI, ConnectedPeerIDType, LocalPeerType, onboardingProgressI, Web3NameListType } from "types/context"; -import { GlobalContext, ReadOnlyWalletMode } from "./GlobalContext"; - +import { useAccount } from 'hooks'; +import useToast from 'hooks/useToast'; +import { MdError } from 'react-icons/md'; +import { useDispatch, useSelector } from 'react-redux'; +import { setUserPushSDKInstance } from 'redux/slices/userSlice'; +import { ConnectedUser } from 'types/chat'; +import { + AppContextType, + BlockedLoadingI, + ConnectedPeerIDType, + LocalPeerType, + onboardingProgressI, + Web3NameListType, +} from 'types/context'; +import { GlobalContext, ReadOnlyWalletMode } from './GlobalContext'; export const AppContext = createContext(null); const AppContextProvider = ({ children }) => { - const { connect, provider, account, wallet, connecting } = useAccount(); - const web3onboardToast = useToast(); - - const { readOnlyWallet } = useContext(GlobalContext); - - const [web3NameList, setWeb3NameList] = useState({}); - const [snapInstalled, setSnapInstalled] = useState(false); - - const [pgpPvtKey, setPgpPvtKey] = useState(null); - const [connectedUser, setConnectedUser] = useState(); - const [localPeer, setLocalPeer] = useState({ - peer: '', - peerID: '' - }) - const [connectedPeerID, setConnectedPeerID] = useState({ - peerID: '' - }); - const [blockedLoading, setBlockedLoading] = useState({ - enabled: false, - title: null, - }); - const [displayQR, setDisplayQR] = useState(false); - - const { userPushSDKInstance } = useSelector((state: any) => { - return state.user; - }); - - const [SnapState, setSnapState] = useState(1); - const { - isModalOpen: isMetamaskPushSnapOpen, - showModal: showMetamaskPushSnap, - ModalComponent: MetamaskPushSnapModalComponent, - } = useModalBlur(); - - const dispatch = useDispatch(); - - const connectWallet = async (showToast = false, toastMessage?: string) =>{ - if (showToast) { - web3onboardToast.showMessageToast({ - toastMessage: toastMessage || "Please connect your wallet to continue", - toastTitle: "Connect Wallet", - toastType: "ERROR", - getToastIcon: (size) => , - }); - } - - if (!(wallet?.accounts?.length > 0)) { - const walletConnected = await connect(); - console.log("Wallet Connected >>>",walletConnected); - if (walletConnected.length > 0) { - return walletConnected[0]; - }else{ - return null; - } - } - + const { connect, provider, account, wallet, connecting } = useAccount(); + const web3onboardToast = useToast(); + + const { readOnlyWallet } = useContext(GlobalContext); + + const [web3NameList, setWeb3NameList] = useState({}); + const [snapInstalled, setSnapInstalled] = useState(false); + + const [pgpPvtKey, setPgpPvtKey] = useState(null); + const [connectedUser, setConnectedUser] = useState(); + const [localPeer, setLocalPeer] = useState({ + peer: '', + peerID: '', + }); + const [connectedPeerID, setConnectedPeerID] = useState({ + peerID: '', + }); + const [blockedLoading, setBlockedLoading] = useState({ + enabled: false, + title: null, + }); + const [displayQR, setDisplayQR] = useState(false); + + const { userPushSDKInstance } = useSelector((state: any) => { + return state.user; + }); + + const [SnapState, setSnapState] = useState(1); + const { + isModalOpen: isMetamaskPushSnapOpen, + showModal: showMetamaskPushSnap, + ModalComponent: MetamaskPushSnapModalComponent, + } = useModalBlur(); + + const dispatch = useDispatch(); + + const connectWallet = async (showToast = false, toastMessage?: string) => { + if (showToast) { + web3onboardToast.showMessageToast({ + toastMessage: toastMessage || 'Please connect your wallet to continue', + toastTitle: 'Connect Wallet', + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); } - const handleConnectWallet = async (showToast = false, toastMessage?: string) => { - if (showToast) { - web3onboardToast.showMessageToast({ - toastMessage: toastMessage || "Please connect your wallet to continue", - toastTitle: "Connect Wallet", - toastType: "ERROR", - getToastIcon: (size) => , - }); - } - - if (wallet?.accounts?.length > 0) { - const userPushInstance = await initializePushSDK(); - return userPushInstance; - } else { - const walletConnected = await connect(); - if (walletConnected.length > 0) { - const userPushInstance = await initializePushSDK(walletConnected[0]); - return userPushInstance; - } else { - return null; - } - } - + if (!(wallet?.accounts?.length > 0)) { + const walletConnected = await connect(); + console.log('Wallet Connected >>>', walletConnected); + if (walletConnected.length > 0) { + return walletConnected[0]; + } else { + return null; + } } - - - - const initialisePushSdkGuestMode = async () => { - let userInstance; - userInstance = await PushAPI.initialize({ - account: readOnlyWallet, - env: appConfig.appEnv, - }); - dispatch(setUserPushSDKInstance(userInstance)); + }; + + const handleConnectWallet = async (showToast = false, toastMessage?: string) => { + if (showToast) { + web3onboardToast.showMessageToast({ + toastMessage: toastMessage || 'Please connect your wallet to continue', + toastTitle: 'Connect Wallet', + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); } - const initialisePushSdkReadMode = async () => { - let userInstance; - userInstance = await PushAPI.initialize({ - env: appConfig.appEnv, - account: account, - }); - dispatch(setUserPushSDKInstance(userInstance)); - return userInstance; + if (wallet?.accounts?.length > 0) { + const userPushInstance = await initializePushSDK(); + return userPushInstance; + } else { + const walletConnected = await connect(); + if (walletConnected.length > 0) { + const userPushInstance = await initializePushSDK(walletConnected[0]); + return userPushInstance; + } else { + return null; + } } + }; + + const initialisePushSdkGuestMode = async () => { + let userInstance; + userInstance = await PushAPI.initialize({ + account: readOnlyWallet, + env: appConfig.appEnv, + alpha: { feature: ['SCALABILITY_V2'] }, + }); + dispatch(setUserPushSDKInstance(userInstance)); + }; + + const initialisePushSdkReadMode = async () => { + let userInstance; + userInstance = await PushAPI.initialize({ + env: appConfig.appEnv, + account: account, + alpha: { feature: ['SCALABILITY_V2'] }, + }); + dispatch(setUserPushSDKInstance(userInstance)); + return userInstance; + }; + + // To reformat errors + const onboardingProgressReformatter = (progressHook: ProgressHookType) => { + let onboardingProgress: onboardingProgressI = { + enabled: true, + hookInfo: progressHook, + spinnerType: LOADER_SPINNER_TYPE.PROCESSING, + progress: 0, + errorMessage: '', + }; + if (progressHook) { + switch (progressHook.progressId) { + case 'PUSH-CREATE-01': + onboardingProgress.hookInfo.progressTitle = 'Creating Push Profile'; + onboardingProgress.progress = 10; + break; + case 'PUSH-CREATE-02': + onboardingProgress.hookInfo.progressTitle = '1/3 - Profile Generation'; + onboardingProgress.progress = 25; + break; + case 'PUSH-CREATE-03': + onboardingProgress.hookInfo.progressTitle = '2/3 - Profile Encryption'; + onboardingProgress.progress = 50; + break; + case 'PUSH-CREATE-04': + onboardingProgress.hookInfo.progressTitle = '3/3 - Profile Sync'; + onboardingProgress.progress = 75; + break; + case 'PUSH-CREATE-05': + onboardingProgress.hookInfo.progressTitle = 'Push Profile Created'; + onboardingProgress.progress = 99; + break; + case 'PUSH-DECRYPT-01': + onboardingProgress.hookInfo.progressTitle = 'Decrypting Push Profile'; + break; + case 'PUSH-DECRYPT-02': + onboardingProgress.enabled = false; + onboardingProgress.hookInfo.progressTitle = 'Push Profile Unlocked'; + break; + // case "PUSH-UPGRADE-01": + // onboardingProgress.hookInfo.progressTitle = "1/4 - Profile Generation"; + // onboardingProgress.progress = 35; + // break; + case 'PUSH-UPGRADE-02': + onboardingProgress.hookInfo.progressTitle = '1/5 - Profile Generation'; + onboardingProgress.progress = 15; + break; + case 'PUSH-AUTH-UPDATE-01': + onboardingProgress.hookInfo.progressTitle = '2/5 - Decrypting Old Profile'; + onboardingProgress.progress = 30; + break; + case 'PUSH-AUTH-UPDATE-02': + onboardingProgress.hookInfo.progressTitle = '3/5 - New Profile Encryption'; + onboardingProgress.progress = 45; + break; + case 'PUSH-AUTH-UPDATE-03': + onboardingProgress.hookInfo.progressTitle = '4/5 - Profile Sync'; + onboardingProgress.progress = 60; + break; + case 'PUSH-AUTH-UPDATE-04': + onboardingProgress.hookInfo.progressTitle = '5/5 - Upgradation Complete'; + onboardingProgress.progress = 75; + break; + // case "PUSH-UPGRADE-03": + // onboardingProgress.hookInfo.progressTitle = "3/4 - New Profile Encryption"; + // onboardingProgress.progress = 75; + // break; + // case "PUSH-UPGRADE-04": + // onboardingProgress.hookInfo.progressTitle = "4/4 - Profile Sync"; + // onboardingProgress.progress = 90; + // break; + case 'PUSH-UPGRADE-05': + onboardingProgress.hookInfo.progressTitle = 'Push Profile Upgraded'; + onboardingProgress.progress = 99; + break; + case 'PUSH-ERROR-00': + onboardingProgress.errorMessage = + 'The sign in was rejected by the user. You can still continue in read-only mode.'; + onboardingProgress.hookInfo.progressTitle = 'Profile Unlock Unsuccessful'; + onboardingProgress.spinnerType = LOADER_SPINNER_TYPE.ERROR; + break; + case 'PUSH-ERROR-01': + onboardingProgress.errorMessage = 'Upgrade Failed'; + onboardingProgress.hookInfo.progressTitle = 'Upgrade Failed'; + onboardingProgress.spinnerType = LOADER_SPINNER_TYPE.ERROR; + break; + case 'PUSH-ERROR-02': + onboardingProgress.errorMessage = 'Decrypting Keys Failed'; + onboardingProgress.hookInfo.progressTitle = 'Decrypting Keys Failed'; + onboardingProgress.spinnerType = LOADER_SPINNER_TYPE.ERROR; + break; + } + } else { + } - - // To reformat errors - const onboardingProgressReformatter = (progressHook: ProgressHookType) => { - let onboardingProgress: onboardingProgressI = { - enabled: true, - hookInfo: progressHook, - spinnerType: LOADER_SPINNER_TYPE.PROCESSING, - progress: 0, - errorMessage: '' - }; - - if (progressHook) { - switch (progressHook.progressId) { - case "PUSH-CREATE-01": - onboardingProgress.hookInfo.progressTitle = "Creating Push Profile"; - onboardingProgress.progress = 10; - break; - case "PUSH-CREATE-02": - onboardingProgress.hookInfo.progressTitle = "1/3 - Profile Generation"; - onboardingProgress.progress = 25; - break; - case "PUSH-CREATE-03": - onboardingProgress.hookInfo.progressTitle = "2/3 - Profile Encryption"; - onboardingProgress.progress = 50; - break; - case "PUSH-CREATE-04": - onboardingProgress.hookInfo.progressTitle = "3/3 - Profile Sync"; - onboardingProgress.progress = 75; - break; - case "PUSH-CREATE-05": - onboardingProgress.hookInfo.progressTitle = "Push Profile Created"; - onboardingProgress.progress = 99; - break; - case "PUSH-DECRYPT-01": - onboardingProgress.hookInfo.progressTitle = "Decrypting Push Profile"; - break; - case "PUSH-DECRYPT-02": - onboardingProgress.enabled = false; - onboardingProgress.hookInfo.progressTitle = "Push Profile Unlocked"; - break; - // case "PUSH-UPGRADE-01": - // onboardingProgress.hookInfo.progressTitle = "1/4 - Profile Generation"; - // onboardingProgress.progress = 35; - // break; - case "PUSH-UPGRADE-02": - onboardingProgress.hookInfo.progressTitle = "1/5 - Profile Generation"; - onboardingProgress.progress = 15; - break; - case "PUSH-AUTH-UPDATE-01": - onboardingProgress.hookInfo.progressTitle = "2/5 - Decrypting Old Profile"; - onboardingProgress.progress = 30; - break; - case "PUSH-AUTH-UPDATE-02": - onboardingProgress.hookInfo.progressTitle = "3/5 - New Profile Encryption"; - onboardingProgress.progress = 45; - break; - case "PUSH-AUTH-UPDATE-03": - onboardingProgress.hookInfo.progressTitle = "4/5 - Profile Sync"; - onboardingProgress.progress = 60; - break; - case "PUSH-AUTH-UPDATE-04": - onboardingProgress.hookInfo.progressTitle = "5/5 - Upgradation Complete"; - onboardingProgress.progress = 75; - break; - // case "PUSH-UPGRADE-03": - // onboardingProgress.hookInfo.progressTitle = "3/4 - New Profile Encryption"; - // onboardingProgress.progress = 75; - // break; - // case "PUSH-UPGRADE-04": - // onboardingProgress.hookInfo.progressTitle = "4/4 - Profile Sync"; - // onboardingProgress.progress = 90; - // break; - case "PUSH-UPGRADE-05": - onboardingProgress.hookInfo.progressTitle = "Push Profile Upgraded"; - onboardingProgress.progress = 99; - break; - case "PUSH-ERROR-00": - onboardingProgress.errorMessage = "The sign in was rejected by the user. You can still continue in read-only mode."; - onboardingProgress.hookInfo.progressTitle = "Profile Unlock Unsuccessful"; - onboardingProgress.spinnerType = LOADER_SPINNER_TYPE.ERROR; - break; - case "PUSH-ERROR-01": - onboardingProgress.errorMessage = "Upgrade Failed"; - onboardingProgress.hookInfo.progressTitle = "Upgrade Failed"; - onboardingProgress.spinnerType = LOADER_SPINNER_TYPE.ERROR; - break; - case "PUSH-ERROR-02": - onboardingProgress.errorMessage = "Decrypting Keys Failed"; - onboardingProgress.hookInfo.progressTitle = "Decrypting Keys Failed"; - onboardingProgress.spinnerType = LOADER_SPINNER_TYPE.ERROR; - break; - } - } else { - - } - - // This is a new user + // This is a new user + setBlockedLoading({ + enabled: onboardingProgress.enabled, + title: onboardingProgress.hookInfo.progressTitle, + spinnerType: onboardingProgress.spinnerType, + progressEnabled: onboardingProgress.progress ? true : false, + progress: onboardingProgress.progress, + progressNotice: onboardingProgress.hookInfo.progressInfo, + errorMessage: onboardingProgress.errorMessage, + }); + }; + + const initializePushSDK = async (wallet?: any) => { + let userInstance; + console.log('Initialising Push General Mode'); + try { + let web3Provider = provider; + let currentAddress = wallet ? wallet.accounts[0].address : account; + + if (wallet) { + web3Provider = new ethers.providers.Web3Provider(wallet.provider, 'any'); + } + + const librarySigner = web3Provider?.getSigner(currentAddress); + userInstance = await PushAPI.initialize(librarySigner!, { + env: appConfig.appEnv, + account: currentAddress, + progressHook: onboardingProgressReformatter, + alpha: { feature: ['SCALABILITY_V2'] }, + }); + if (userInstance) { setBlockedLoading({ - enabled: onboardingProgress.enabled, - title: onboardingProgress.hookInfo.progressTitle, - spinnerType: onboardingProgress.spinnerType, - progressEnabled: onboardingProgress.progress ? true : false, - progress: onboardingProgress.progress, - progressNotice: onboardingProgress.hookInfo.progressInfo, - errorMessage: onboardingProgress.errorMessage, + enabled: false, + title: 'Push Profile Setup Complete', + spinnerType: LOADER_SPINNER_TYPE.COMPLETED, + progressEnabled: false, + progress: 100, }); + } + + dispatch(setUserPushSDKInstance(userInstance)); + return userInstance; + } catch (error) { + // Handle initialization error + console.log('Errror !!!!!', error); + return null; + } + }; - }; + const getUser = async () => { + const caip10: string = w2wHelper.walletToCAIP10({ account }); + const user = await userPushSDKInstance.info(); + let connectedUser: ConnectedUser; - const initializePushSDK = async (wallet?: any) => { - let userInstance; - console.log("Initialising Push General Mode"); - try { - - let web3Provider = provider; - let currentAddress = wallet ? wallet.accounts[0].address : account; - - if (wallet) { - web3Provider = new ethers.providers.Web3Provider(wallet.provider, 'any') - } - - const librarySigner = web3Provider?.getSigner(currentAddress); - userInstance = await PushAPI.initialize(librarySigner!, { - env: appConfig.appEnv, - account: currentAddress, - progressHook: onboardingProgressReformatter, - }); - if (userInstance) { - setBlockedLoading({ - enabled: false, - title: "Push Profile Setup Complete", - spinnerType: LOADER_SPINNER_TYPE.COMPLETED, - progressEnabled: false, - progress: 100, - }); - } - - dispatch(setUserPushSDKInstance(userInstance)); - return userInstance; - } catch (error) { - // Handle initialization error - console.log("Errror !!!!!", error); - return null; - } - }; + // TODO: Change this to do verification on ceramic to validate if did is valid + if (user?.did.includes('did:3:')) { + throw Error('Invalid DID'); + } - const getUser = async () => { - const caip10: string = w2wHelper.walletToCAIP10({ account }); - const user = await userPushSDKInstance.info(); - let connectedUser: ConnectedUser; - - // TODO: Change this to do verification on ceramic to validate if did is valid - if (user?.did.includes('did:3:')) { - throw Error('Invalid DID'); - } - - // new user might not have a private key - if (user && user.encryptedPrivateKey) { - if (user.wallets.includes(',') || !user.wallets?.toLowerCase().includes(caip10?.toLowerCase())) { - throw Error('Invalid user'); - } - const privateKeyArmored = userPushSDKInstance.decryptedPgpPvtKey; - setPgpPvtKey(privateKeyArmored); - connectedUser = { ...user, privateKey: privateKeyArmored }; - } else { - //TODO: This will not be needed now because push user is created and info has user and user.encryptedPrivateKey both - connectedUser = { - // We only need to provide this information when it's a new user - name: 'john-snow', - profilePicture: - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAvklEQVR4AcXBsW2FMBiF0Y8r3GQb6jeBxRauYRpo4yGQkMd4A7kg7Z/GUfSKe8703fKDkTATZsJsrr0RlZSJ9r4RLayMvLmJjnQS1d6IhJkwE2bT13U/DBzp5BN73xgRZsJMmM1HOolqb/yWiWpvjJSUiRZWopIykTATZsJs5g+1N6KSMiO1N/5DmAkzYTa9Lh6MhJkwE2ZzSZlo7xvRwson3txERzqJhJkwE2bT6+JhoKTMJ2pvjAgzYSbMfgDlXixqjH6gRgAAAABJRU5ErkJggg==', - wallets: caip10, - about: '', - allowedNumMsg: 0, - did: caip10, - encryptedPrivateKey: '', - encryptionType: '', - numMsg: 0, - publicKey: '', - sigType: '', - signature: '', - linkedListHash: '', - privateKey: '', - }; - } - - setConnectedUser(connectedUser); - }; + // new user might not have a private key + if (user && user.encryptedPrivateKey) { + if (user.wallets.includes(',') || !user.wallets?.toLowerCase().includes(caip10?.toLowerCase())) { + throw Error('Invalid user'); + } + const privateKeyArmored = userPushSDKInstance.decryptedPgpPvtKey; + setPgpPvtKey(privateKeyArmored); + connectedUser = { ...user, privateKey: privateKeyArmored }; + } else { + //TODO: This will not be needed now because push user is created and info has user and user.encryptedPrivateKey both + connectedUser = { + // We only need to provide this information when it's a new user + name: 'john-snow', + profilePicture: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAvklEQVR4AcXBsW2FMBiF0Y8r3GQb6jeBxRauYRpo4yGQkMd4A7kg7Z/GUfSKe8703fKDkTATZsJsrr0RlZSJ9r4RLayMvLmJjnQS1d6IhJkwE2bT13U/DBzp5BN73xgRZsJMmM1HOolqb/yWiWpvjJSUiRZWopIykTATZsJs5g+1N6KSMiO1N/5DmAkzYTa9Lh6MhJkwE2ZzSZlo7xvRwson3txERzqJhJkwE2bT6+JhoKTMJ2pvjAgzYSbMfgDlXixqjH6gRgAAAABJRU5ErkJggg==', + wallets: caip10, + about: '', + allowedNumMsg: 0, + did: caip10, + encryptedPrivateKey: '', + encryptionType: '', + numMsg: 0, + publicKey: '', + sigType: '', + signature: '', + linkedListHash: '', + privateKey: '', + }; + } - useEffect(() => { - const librarySigner = provider?.getSigner(account); - // if (!account || !appConfig?.appEnv) return; - if (wallet?.accounts?.length > 0) { - initialisePushSdkReadMode(); - } else { - initialisePushSdkGuestMode(); - } - }, [account]); - - const createUserIfNecessary = async (): Promise => { - try { - const signer = await provider.getSigner(); - await PushAPI.user.create({ - account: account, - env: appConfig.appEnv, - signer: signer, - progressHook: onboardingProgressReformatter - }); - const createdUser = await PushAPI.user.get({ - account: account, - env: appConfig.appEnv - }); - const pvtkey = await PushAPI.chat.decryptPGPKey({ - encryptedPGPPrivateKey: createdUser.encryptedPrivateKey, - signer: signer, - env: appConfig.appEnv, - toUpgrade: true, - progressHook: onboardingProgressReformatter - }); - - const createdConnectedUser = { ...createdUser, privateKey: pvtkey }; - setConnectedUser(createdConnectedUser); - setPgpPvtKey(pvtkey); - - return createdConnectedUser; - } catch (e) { - console.error(e); - } - }; + setConnectedUser(connectedUser); + }; - return ( - - {children} - - ) -} - -export default AppContextProvider \ No newline at end of file + useEffect(() => { + const librarySigner = provider?.getSigner(account); + // if (!account || !appConfig?.appEnv) return; + if (wallet?.accounts?.length > 0) { + initialisePushSdkReadMode(); + } else { + initialisePushSdkGuestMode(); + } + }, [account]); + + const createUserIfNecessary = async (): Promise => { + try { + const signer = await provider.getSigner(); + await PushAPI.user.create({ + account: account, + env: appConfig.appEnv, + signer: signer, + progressHook: onboardingProgressReformatter, + }); + const createdUser = await PushAPI.user.get({ + account: account, + env: appConfig.appEnv, + }); + const pvtkey = await PushAPI.chat.decryptPGPKey({ + encryptedPGPPrivateKey: createdUser.encryptedPrivateKey, + signer: signer, + env: appConfig.appEnv, + toUpgrade: true, + progressHook: onboardingProgressReformatter, + }); + + const createdConnectedUser = { ...createdUser, privateKey: pvtkey }; + setConnectedUser(createdConnectedUser); + setPgpPvtKey(pvtkey); + + return createdConnectedUser; + } catch (e) { + console.error(e); + } + }; + + return ( + + {children} + + ); +}; + +export default AppContextProvider; diff --git a/src/hooks/useModalBlur.tsx b/src/hooks/useModalBlur.tsx index 7fec633c83..2fc64e901c 100644 --- a/src/hooks/useModalBlur.tsx +++ b/src/hooks/useModalBlur.tsx @@ -34,8 +34,8 @@ export type ModalType = { export const MODAL_POSITION = { ON_ROOT: 1, - ON_PARENT: 2 -} + ON_PARENT: 2, +}; const useModalBlur = () => { const [open, setOpen] = React.useState(false); @@ -49,7 +49,6 @@ const useModalBlur = () => { document.body.style.overflow = 'hidden'; } document.body.style.paddingRight = '1rem'; - } else { document.body.style.overflow = 'unset'; document.body.style.paddingRight = '0px'; diff --git a/src/index.css b/src/index.css index 86c621ae2b..5f5b50936a 100644 --- a/src/index.css +++ b/src/index.css @@ -597,6 +597,6 @@ button, a, a:before, a:after { margin-right: auto; border-radius: var(--w3o-border-radius); overflow: auto; - z-index: 99999; + z-index: 9999999; } diff --git a/src/modules/chat/ChatModule.tsx b/src/modules/chat/ChatModule.tsx index 17ac1ec352..e88bcf7c23 100644 --- a/src/modules/chat/ChatModule.tsx +++ b/src/modules/chat/ChatModule.tsx @@ -41,10 +41,11 @@ import VideoCallSection from 'sections/video/VideoCallSection'; import { ChatUserAppContext, Feeds, MessageIPFS, MessageIPFSWithCID, User, VideoCallInfoI } from 'types/chat'; // Internal Configs -import { ChatUIProvider, darkChatTheme } from '@pushprotocol/uiweb'; +import { ChatUIProvider, UserProfile, darkChatTheme } from '@pushprotocol/uiweb'; import { appConfig } from 'config'; import GLOBALS, { device, globalsMargin } from 'config/Globals'; import { GlobalContext } from 'contexts/GlobalContext'; +import UnlockProfile from 'components/chat/unlockProfile/UnlockProfile'; export const ToastPosition: ToastOptions = { position: 'top-right', @@ -72,7 +73,7 @@ function Chat({ chatid }) { setConnectedUser, displayQR, setDisplayQR, - handleConnectWallet + handleConnectWallet, } = useContext(AppContext); const { userPushSDKInstance } = useSelector((state: any) => { @@ -110,31 +111,28 @@ function Chat({ chatid }) { let userPushInstance = userPushSDKInstance; if (!formattedChatParticipant.includes('.')) { - if (!await ethers.utils.isAddress(caip10ToWallet(formattedChatParticipant))) - formattedChatParticipant = chatId; + if (!(await ethers.utils.isAddress(caip10ToWallet(formattedChatParticipant)))) formattedChatParticipant = chatId; } let formattedchatId = reformatChatId(formattedChatParticipant); //If no PGP keys then connect the wallet. - if (!userPushInstance.decryptedPgpPvtKey) { - userPushInstance = await handleConnectWallet(); - - if (userPushInstance && userPushInstance.decryptedPgpPvtKey) { + if (!userPushInstance.readmode()) { + if (userPushInstance && !userPushInstance.readmode()) { navigate(`/chat/${formattedchatId}`); return formattedChatParticipant; } - }else{ + } else { navigate(`/chat/${formattedchatId}`); return formattedChatParticipant; } - } + }; useEffect(() => { if ( connectedUser && socketData.messagesSinceLastConnection && w2wHelper.caip10ToWallet(socketData.messagesSinceLastConnection.fromCAIP10).toLowerCase() !== - account.toLowerCase() + account.toLowerCase() ) { if (currentChat) getUpdatedInbox(socketData.messagesSinceLastConnection); } @@ -268,6 +266,11 @@ function Chat({ chatid }) { ModalComponent: GroupInfoModalComponent, } = useModalBlur(); + const { + isModalOpen: isUnlockProfileOpen, + showModal: showModal, + ModalComponent: UnlockProfileModalComponent, + } = useModalBlur(); const createGroupToast = useToast(); const { @@ -398,9 +401,11 @@ function Chat({ chatid }) { // navigate(`/chat`); } }; - useEffect(() => { + if (userPushSDKInstance?.readmode()) showModal(); + }, [userPushSDKInstance]); + useEffect(() => { let formattedchatId = selectedChatId || chatid; if (formattedchatId) { @@ -413,7 +418,7 @@ function Chat({ chatid }) { } }, [selectedChatId]); - useEffect(() => { }, [account, connectedUser?.privateKey]); + useEffect(() => {}, [account, connectedUser?.privateKey]); return ( @@ -474,14 +479,23 @@ function Chat({ chatid }) { padding="10px 10px 10px 10px" chatActive={viewChatBox} > - + {userPushSDKInstance && userPushSDKInstance?.readmode() && ( + {}} + toastObject={groupInfoToast} + modalPadding="0px" + modalPosition={MODAL_POSITION.ON_PARENT} + /> + )} { }} + onConfirm={() => {}} toastObject={groupInfoToast} modalPadding="0px" modalPosition={MODAL_POSITION.ON_PARENT} @@ -572,19 +586,22 @@ const Container = styled.div` box-sizing: border-box; margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.DESKTOP}; - height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.DESKTOP.TOP} - ${globalsMargin.MINI_MODULES.DESKTOP.BOTTOM - }); + height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.DESKTOP.TOP} - ${ + globalsMargin.MINI_MODULES.DESKTOP.BOTTOM +}); @media ${device.laptop} { margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.TABLET}; - height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.TABLET.TOP} - ${globalsMargin.MINI_MODULES.TABLET.BOTTOM - }); + height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.TABLET.TOP} - ${ + globalsMargin.MINI_MODULES.TABLET.BOTTOM +}); } @media ${device.mobileL} { margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.MOBILE}; - height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.MOBILE.TOP} - ${globalsMargin.MINI_MODULES.MOBILE.BOTTOM - }); + height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.MOBILE.TOP} - ${ + globalsMargin.MINI_MODULES.MOBILE.BOTTOM +}); border: ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE}; `; diff --git a/src/sections/chat/ChatSidebarSection.tsx b/src/sections/chat/ChatSidebarSection.tsx index 6d2ee7a56e..6bc6b72757 100644 --- a/src/sections/chat/ChatSidebarSection.tsx +++ b/src/sections/chat/ChatSidebarSection.tsx @@ -34,7 +34,6 @@ import { GlobalContext } from 'contexts/GlobalContext'; import { useAccount } from 'hooks'; import { appConfig } from '../../config'; - const createGroupOnMouseEnter = [ { name: 'create-group-fill-icon', @@ -61,7 +60,7 @@ const createGroupOnMouseLeave = [ }, ]; -type loadingData = { loading: boolean, preload: boolean, paging: boolean, finished: boolean }; +type loadingData = { loading: boolean; preload: boolean; paging: boolean; finished: boolean }; // Chat Sections // Divided into two, left and right @@ -76,7 +75,8 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha const isNewTagVisible = getIsNewTagVisible(new Date('2023-02-22T00:00:00.000'), 90); - const { connectedUser, displayQR, setDisplayQR, initializePushSDK, handleConnectWallet, connectWallet } = useContext(AppContext); + const { connectedUser, displayQR, setDisplayQR, initializePushSDK, handleConnectWallet, connectWallet } = + useContext(AppContext); const [searchedUser, setSearchedUser] = useState(''); const { activeTab, setActiveTab } = useContext(Context); @@ -102,7 +102,7 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha let navigate = useNavigate(); const handleCreateGroup = async () => { - if (userPushSDKInstance.decryptedPgpPvtKey) { + if (!userPushSDKInstance.readmode()) { showCreateGroupModal(); } else { if (userPushSDKInstance.account === readOnlyWallet) { @@ -114,7 +114,7 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha } } } - } + }; // RENDER return ( @@ -159,14 +159,14 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha color={theme.default.color} zIndex="1" flex="1" - padding="10px 10px 20px 10px" + padding="10px 10px 8px 10px" onClick={() => { setActiveTab(1); }} > Requests - {(requestLoadingData && requestLoadingData?.loading) && ( + {requestLoadingData && requestLoadingData?.loading && ( )} - {(requestLoadingData && !requestLoadingData?.loading) && - requestChatList.length > 0 && ( - - {requestChatList.length} - - )} + {requestLoadingData && !requestLoadingData?.loading && requestChatList.length > 0 && ( + + {requestChatList.length} + + )} @@ -218,37 +217,8 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha autofilled={undefined} searchedUser={searchedUser} setSearchedUser={setSearchedUser} - /> )} - {activeTab == 0 && ( - StyleHelper.changeStyle(createGroupOnMouseEnter)} - onMouseLeave={() => StyleHelper.changeStyle(createGroupOnMouseLeave)} - > - - - - Create Group - - {isNewTagVisible && } - - )} {/* Set Chats */} {/* Only show recommended chats if there are no chats */} - {showRecommended && + {showRecommended && ( { setSelectedChatId(await triggerChatParticipant(chatParticipant, chatid)) }} + onChatSelected={async (chatid, chatParticipant) => { + setSelectedChatId(await triggerChatParticipant(chatParticipant, chatid)); + }} /> - } + )} {/* Only show recommended chats if there are no chats */} - {!showRecommended && + {!showRecommended && ( { setSelectedChatId(await triggerChatParticipant(chatParticipant, chatid)) }} - + onChatSelected={async (chatid, chatParticipant) => { + setSelectedChatId(await triggerChatParticipant(chatParticipant, chatid)); + }} onUnreadCountChange={(count) => { // console.log('Count is: ', count); }} @@ -283,7 +256,7 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha } }} /> - } + )} {/* Set Requests */} @@ -296,14 +269,15 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha > { setSelectedChatId(await triggerChatParticipant(chatParticipant, chatid)) }} + onChatSelected={async (chatid, chatParticipant) => { + setSelectedChatId(await triggerChatParticipant(chatParticipant, chatid)); + }} onUnreadCountChange={(count) => { // console.log('Count is: ', count); }} onLoading={(loadingData) => setRequestLoadingData(loadingData)} onPaging={(chats) => setRequestChatList(chats)} onPreload={(chats) => setRequestChatList(chats)} - /> @@ -332,15 +306,43 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha setSelectedChatId(await triggerChatParticipant(chatParticipant, chatid))} + onChatSelected={async (chatid, chatParticipant) => + setSelectedChatId(await triggerChatParticipant(chatParticipant, chatid)) + } onUnreadCountChange={(count) => { // console.log('Count is: ', count); }} /> )} - - + {activeTab == 3 && ( + StyleHelper.changeStyle(createGroupOnMouseEnter)} + onMouseLeave={() => StyleHelper.changeStyle(createGroupOnMouseLeave)} + > + + + + Create Group + + {isNewTagVisible && } + + )} {/* Footer */} @@ -359,7 +361,10 @@ const ChatSidebarSection = ({ showCreateGroupModal, autofilledSearch, triggerCha ) : null} - + {/* =16.8.0" styled-components: ^6.0.8 viem: ^1.3.0 - checksum: 25cf0f2f0990ac99e598c90bd602031f1f8dd299b674d50ea32c46fe39a6939dd912d08a5040056cf5583d7fcd33c96f966827c8ca9dfd6e1f088376d24cc200 + checksum: 9134c4033b8422db6b087c41713ea5fd8f80176008e541451e2f7a9714d1d504584159bf630039bbd24954670b6c33a7ac6a17637a05ed33eac749b58277b3ca languageName: node linkType: hard