diff --git a/electron/main.js b/electron/main.js index 69d1620b..66083c3c 100644 --- a/electron/main.js +++ b/electron/main.js @@ -117,15 +117,18 @@ const createMainWindow = () => { // only open valid https urls to prevent remote execution // will throw if url isn't valid const validatedUrl = new URL(originalUrl); - if (validatedUrl.protocol !== 'https:') { - throw Error(`can't open url '${originalUrl}' not https`); + let serializedUrl = ''; + + // make an exception for ipfs stats + if (validatedUrl.toString() === 'http://localhost:5001/webui/') { + serializedUrl = validatedUrl.toString(); + } else if (validatedUrl.protocol === 'https:') { + // open serialized url to prevent remote execution + serializedUrl = validatedUrl.toString(); + } else { + throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`); } - // do not open http protocol, not private - // do not open any other protocol, will lead to remote execution - - // open serialized url to prevent remote execution - const serializedUrl = validatedUrl.toString(); shell.openExternal(serializedUrl); } catch (e) { console.warn(e); @@ -136,7 +139,7 @@ const createMainWindow = () => { // open links (with target="_blank") in external browser // do not open links in plebchan or will lead to remote execution mainWindow.webContents.setWindowOpenHandler(({url}) => { - const originalUrl = url + const originalUrl = url; try { // do not let the user open any url with shell.openExternal // or it will lead to remote execution https://benjamin-altpeter.de/shell-openexternal-dangers/ @@ -144,25 +147,26 @@ const createMainWindow = () => { // only open valid https urls to prevent remote execution // will throw if url isn't valid const validatedUrl = new URL(originalUrl); - if (validatedUrl.protocol !== 'https:') { - throw Error(`can't open url '${originalUrl}' not https`); + let serializedUrl = ''; + + // make an exception for ipfs stats + if (validatedUrl.toString() === 'http://localhost:5001/webui/') { + serializedUrl = validatedUrl.toString(); + } else if (validatedUrl.protocol === 'https:') { + // open serialized url to prevent remote execution + serializedUrl = validatedUrl.toString(); + } else { + throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`); } - // do not open http protocol, not private - // do not open any other protocol, will lead to remote execution - - // open serialized url to prevent remote execution - const serializedUrl = validatedUrl.toString(); shell.openExternal(serializedUrl); - setImmediate(() => { - shell.openExternal(serializedUrl) - }) } catch (e) { console.warn(e); } - return {action: 'deny'} + return {action: 'deny'}; }) + // deny permissions like location, notifications, etc https://www.electronjs.org/docs/latest/tutorial/security#5-handle-session-permission-requests-from-remote-content mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => { // deny all permissions diff --git a/electron/preload.js b/electron/preload.js index ee5de106..80bf9917 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -38,6 +38,9 @@ const defaultPlebbitOptions = { ipfsGatewayUrls: ['http://localhost:11028'] || undefined, }; +// expose a flag to indicate that we are running in electron +contextBridge.exposeInMainWorld('electron', { isElectron: true }); + // expose plebbit-js native functions into electron's renderer contextBridge.exposeInMainWorld('plebbitJsNativeFunctions', require('@plebbit/plebbit-js').nativeFunctions.node) contextBridge.exposeInMainWorld('defaultPlebbitOptions', defaultPlebbitOptions) @@ -47,4 +50,4 @@ try { if (isDev) localStorage.debug = 'plebbit-js:*,plebbit-react-hooks:*,plebchan:*'; } catch (e) { console.log(e); -} +} \ No newline at end of file diff --git a/src/components/VerifiedAuthor.jsx b/src/components/VerifiedAuthor.jsx new file mode 100644 index 00000000..5a832381 --- /dev/null +++ b/src/components/VerifiedAuthor.jsx @@ -0,0 +1,10 @@ +import {useComment, useAuthorAddress} from '@plebbit/plebbit-react-hooks'; + +function VerifiedAuthor({ commentCid, children }) { + const comment = useComment({commentCid}); + const {authorAddress, shortAuthorAddress} = useAuthorAddress({comment}); + + return children({ authorAddress, shortAuthorAddress }); +} + +export default VerifiedAuthor; \ No newline at end of file diff --git a/src/components/modals/CaptchaModal.jsx b/src/components/modals/CaptchaModal.jsx index 4477766c..91a7dc2d 100644 --- a/src/components/modals/CaptchaModal.jsx +++ b/src/components/modals/CaptchaModal.jsx @@ -95,6 +95,7 @@ const CaptchaModal = () => { const handleCloseModal = () => { setImageSources([]); setChallengesArray([]); + setCurrentChallengeIndex(0); setIsCaptchaOpen(false); }; diff --git a/src/components/modals/ModerationModal.jsx b/src/components/modals/ModerationModal.jsx index 44caad8d..9188516a 100755 --- a/src/components/modals/ModerationModal.jsx +++ b/src/components/modals/ModerationModal.jsx @@ -50,7 +50,8 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => { if (challengeVerification.challengeSuccess === true) { setNewSuccessMessage('Challenge Success'); } else if (challengeVerification.challengeSuccess === false) { - setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors}); + setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`); + console.log('challenge failed', challengeVerification); } }; @@ -63,7 +64,7 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => { challengeAnswers = await getChallengeAnswersFromUser(challenges) } catch (error) { - setNewErrorMessage(error); + setNewErrorMessage(error.message); console.log(error); } if (challengeAnswers) { await comment.publishChallengeAnswers(challengeAnswers) @@ -113,9 +114,19 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => { onChallenge, onChallengeVerification, onError: (error) => { - setNewErrorMessage(error); + setNewErrorMessage(error.message); console.log(error); }, }); + + + useEffect(() => { + if (selectedAddress) { + setPublishCommentEditOptions((prevOptions) => ({ + ...prevOptions, + subplebbitAddress: selectedAddress, + })); + } + }, [selectedAddress]); const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); diff --git a/src/components/modals/ReplyModal.jsx b/src/components/modals/ReplyModal.jsx index a73ed331..96fb2235 100755 --- a/src/components/modals/ReplyModal.jsx +++ b/src/components/modals/ReplyModal.jsx @@ -99,10 +99,10 @@ const ReplyModal = ({ isOpen, closeModal }) => { const onChallengeVerification = (challengeVerification) => { if (challengeVerification.challengeSuccess === true) { - console.log('challenge success'); - } - else if (challengeVerification.challengeSuccess === false) { - setNewErrorMessage('challenge failed', {reason: challengeVerification.reason, errors: challengeVerification.errors}); + return; + } else if (challengeVerification.challengeSuccess === false) { + setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`); + console.log('challenge failed', challengeVerification); } }; @@ -115,7 +115,7 @@ const ReplyModal = ({ isOpen, closeModal }) => { challengeAnswers = await getChallengeAnswersFromUser(challenges) } catch (error) { - setNewErrorMessage(error); + setNewErrorMessage(error.message); console.log(error); } if (challengeAnswers) { await comment.publishChallengeAnswers(challengeAnswers) @@ -136,7 +136,7 @@ const ReplyModal = ({ isOpen, closeModal }) => { onChallenge, onChallengeVerification, onError: (error) => { - setNewErrorMessage(error); + setNewErrorMessage(error.message); console.log(error); }, }); @@ -229,8 +229,10 @@ const ReplyModal = ({ isOpen, closeModal }) => { }, [selectedParentCid, anonymousMode, account]); useEffect(() => { - updateSigner(); - }, [updateSigner]); + if (anonymousMode) { + updateSigner(); + } + }, [updateSigner, anonymousMode]); useEffect(() => { diff --git a/src/components/modals/SettingsModal.jsx b/src/components/modals/SettingsModal.jsx index f60d28bc..144e847d 100755 --- a/src/components/modals/SettingsModal.jsx +++ b/src/components/modals/SettingsModal.jsx @@ -36,6 +36,17 @@ const SettingsModal = ({ isOpen, closeModal }) => { const importRef = useRef(); const nameRef = useRef(); + const defaultGatewayUrls = [ + 'https://ipfs.io', + 'https://ipfsgateway.xyz', + 'https://cloudflare-ipfs.com', + 'https://plebpubsub.live' + ]; + const defaultPubsubHttpClientsOptions = [ + 'https://pubsubprovider.xyz/api/v0', + 'https://plebpubsub.live/api/v0' + ]; + const isValidURL = (url) => { try { new URL(url); @@ -46,14 +57,26 @@ const SettingsModal = ({ isOpen, closeModal }) => { }; const handleSavePlebbitOptions = async () => { - const gatewayUrls = gatewayRef.current.value.split('\n').filter(url => url.trim()); - const ipfsClientsOptions = ipfsRef.current.value.split('\n').filter(url => url.trim()) || undefined; - const pubsubClientsOptions = pubsubRef.current.value.split('\n').filter(url => url.trim()); + let gatewayUrls = gatewayRef.current.value.split('\n').filter(url => url.trim()); + let ipfsClientsOptions = ipfsRef.current.value.split('\n').filter(url => url.trim()); + let pubsubClientsOptions = pubsubRef.current.value.split('\n').filter(url => url.trim()); + + if (!gatewayUrls.length) { + gatewayUrls = defaultGatewayUrls; + } + + if (!ipfsClientsOptions.length) { + ipfsClientsOptions = undefined; + } + + if (!pubsubClientsOptions.length) { + pubsubClientsOptions = defaultPubsubHttpClientsOptions; + } const invalidUrls = [ - ...gatewayUrls, + ...gatewayUrls || defaultGatewayUrls, ...(ipfsClientsOptions || []), - ...pubsubClientsOptions, + ...pubsubClientsOptions || defaultPubsubHttpClientsOptions, ].filter((url) => !isValidURL(url)); if (invalidUrls.length > 0) { @@ -72,7 +95,7 @@ const SettingsModal = ({ isOpen, closeModal }) => { localStorage.setItem("successToast", "Settings Saved"); window.location.reload(); } catch (error) { - setNewErrorMessage(error.message); + setNewErrorMessage(error.message); console.log(error); } }; @@ -81,13 +104,6 @@ const SettingsModal = ({ isOpen, closeModal }) => { setNewErrorMessage(null); setNewSuccessMessage(null); - const defaultGatewayUrls = [ - 'https://ipfs.io', - 'https://ipfsgateway.xyz', - 'https://cloudflare-ipfs.com' - ]; - const defaultPubsubHttpClientsOptions = ['https://pubsubprovider.xyz/api/v0']; - gatewayRef.current.value = defaultGatewayUrls.join('\n'); ipfsRef.current.value = ""; pubsubRef.current.value = defaultPubsubHttpClientsOptions.join('\n'); @@ -105,7 +121,7 @@ const SettingsModal = ({ isOpen, closeModal }) => { localStorage.setItem("successToast", "Settings Reset"); window.location.reload(); } catch (error) { - setNewErrorMessage(error.message); + setNewErrorMessage(error.message); console.log(error); } }; @@ -169,7 +185,7 @@ const SettingsModal = ({ isOpen, closeModal }) => { setNewSuccessMessage("Account Imported"); } catch (error) { - setNewErrorMessage(error.message); + setNewErrorMessage(error.message); console.log(error); } }; @@ -190,7 +206,7 @@ const SettingsModal = ({ isOpen, closeModal }) => { }}); setNewSuccessMessage("Account Name Saved"); } catch (error) { - setNewErrorMessage(error.message); + setNewErrorMessage(error.message); console.log(error); } }; @@ -207,7 +223,13 @@ const SettingsModal = ({ isOpen, closeModal }) => {
- v{version} + v{version} -  + {window.electron && window.electron.isElectron ? ( + + full node + + ) : (web) + } Settings diff --git a/src/components/styled/modals/SettingsModal.styled.jsx b/src/components/styled/modals/SettingsModal.styled.jsx index 094a4b5e..806afb47 100644 --- a/src/components/styled/modals/SettingsModal.styled.jsx +++ b/src/components/styled/modals/SettingsModal.styled.jsx @@ -417,4 +417,8 @@ export const StyledModal = styled(Modal)` return ''; } }} + + #node-stats { + text-decoration: none; + } `; \ No newline at end of file diff --git a/src/components/styled/views/Board.styled.jsx b/src/components/styled/views/Board.styled.jsx index f3ef6856..c2cb1312 100644 --- a/src/components/styled/views/Board.styled.jsx +++ b/src/components/styled/views/Board.styled.jsx @@ -267,7 +267,7 @@ export const Header = styled.div` clear: both; .banner { - border: 1px solid #800; + border: 1px solid #34345c; margin: 5px auto; width: 300px; height: 100px; @@ -2016,6 +2016,16 @@ export const BoardForm = styled.div` } video { + width: 100%; + height: auto; + } + + iframe { + width: 100%; + height: auto; + } + + #video-thumbnail-mobile { width: 100px; } @@ -3804,7 +3814,7 @@ export const BoardForm = styled.div` color: #707070; font-weight: 700; background-color: rgb(27,28,30); - border: 1px solid #282A2E; + border: 1px solid #2D2F33; background-image: url(assets/buttonfade-dark.png); background-repeat: repeat-x; text-decoration: none; diff --git a/src/components/styled/views/Thread.styled.jsx b/src/components/styled/views/Thread.styled.jsx index 6c1f8cc2..09b9c343 100644 --- a/src/components/styled/views/Thread.styled.jsx +++ b/src/components/styled/views/Thread.styled.jsx @@ -1193,7 +1193,13 @@ export const BoardForm = styled.div` } video { - width: 100px; + width: 100%; + height: auto; + } + + iframe { + width: 100%; + height: auto; } img { @@ -2932,7 +2938,7 @@ export const BoardForm = styled.div` color: #707070; font-weight: 700; background-color: rgb(27,28,30); - border: 1px solid #282A2E; + border: 1px solid #2D2F33; background-image: url(assets/buttonfade-dark.png); background-repeat: repeat-x; text-decoration: none; diff --git a/src/components/views/All.jsx b/src/components/views/All.jsx index 34dd3b47..d984ba16 100755 --- a/src/components/views/All.jsx +++ b/src/components/views/All.jsx @@ -1,4 +1,4 @@ -import React, { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useState, useRef } from 'react'; +import React, { Fragment, useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { confirmAlert } from 'react-confirm-alert'; import { createPortal } from 'react-dom'; import { Helmet } from 'react-helmet-async'; @@ -21,6 +21,7 @@ import Post from '../Post'; import PostLoader from '../PostLoader'; import PostOnHover from '../PostOnHover'; import StateLabel from '../StateLabel'; +import VerifiedAuthor from '../VerifiedAuthor'; import ReplyModal from '../modals/ReplyModal'; import SettingsModal from '../modals/SettingsModal'; import findShortParentCid from '../../utils/findShortParentCid'; @@ -102,6 +103,8 @@ const All = () => { const [moderatorPermissions, setModeratorPermissions] = useState({}); const [executeAnonMode, setExecuteAnonMode] = useState(false); const [cidTracker, setCidTracker] = useState({}); + const [isThumbnailClicked, setIsThumbnailClicked] = useState({}); + const [isMobileThumbnailClicked, setIsMobileThumbnailClicked] = useState({}); const setSelectedThreadCid = (cid) => { selectedThreadCidRef.current = cid; @@ -117,6 +120,20 @@ const All = () => { const stateString = useFeedStateString(subplebbits); + const handleThumbnailClick = (index, isMobile=false) => { + if (isMobile) { + setIsMobileThumbnailClicked(prevState => ({ + ...prevState, + [index]: !prevState[index], + })); + } else { + setIsThumbnailClicked(prevState => ({ + ...prevState, + [index]: !prevState[index], + })); + } + }; + useEffect(() => { let permissions = {}; @@ -163,6 +180,14 @@ const All = () => { }, [openMenuCid, handleOutsideClick]); + useEffect(() => { + if (postOnHoverRef.current) { + const rect = postOnHoverRef.current.getBoundingClientRect(); + setPostOnHoverHeight(rect.height); + } + }, [outOfViewCid]); + + const errorString = useMemo(() => { for (const subplebbit of subplebbits) { if (subplebbit?.updatingState !== 'failed') { @@ -279,7 +304,8 @@ const All = () => { setNewSuccessMessage('Challenge Success'); } else if (challengeVerification.challengeSuccess === false) { - setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors}); + setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`); + console.log('challenge failed', challengeVerification); } }; @@ -290,7 +316,7 @@ const All = () => { challengeAnswers = await getChallengeAnswersFromUser(challenges) } catch (error) { - setNewErrorMessage(error); + setNewErrorMessage(error.message); console.log(error); } if (challengeAnswers) { await comment.publishChallengeAnswers(challengeAnswers) @@ -347,7 +373,7 @@ const All = () => { onChallenge, onChallengeVerification, onError: (error) => { - setNewErrorMessage(error); + setNewErrorMessage(error.message); console.log(error); }, }); @@ -355,8 +381,8 @@ const All = () => { const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); - const handleAuthorDeleteClick = (cid) => { - handleOptionClick(cid); + const handleAuthorDeleteClick = (comment) => { + handleOptionClick(comment.cid); confirmAlert({ customUI: ({ onClose }) => { @@ -370,9 +396,10 @@ const All = () => { onClick={() => { setIsAuthorDelete(true); setIsAuthorEdit(false); - setCommentCid(cid); setPublishCommentEditOptions(prevOptions => ({ ...prevOptions, + commentCid: comment.cid, + subplebbitAddress: comment.subplebbitAddress, deleted: true, })); setTriggerPublishCommentEdit(true); @@ -457,14 +484,6 @@ const All = () => { }; - useLayoutEffect(() => { - if (postOnHoverRef.current) { - const rect = postOnHoverRef.current.getBoundingClientRect(); - setPostOnHoverHeight(rect.height); - } - }, [outOfViewCid]); - - return ( <> @@ -583,7 +602,7 @@ const All = () => { Catalog ]
- {feed.length > 0 ? ( + {feed ? ( null ) : (
@@ -599,7 +618,7 @@ const All = () => {
- {feed.length > 0 ? ( + {feed ? ( {
{commentMediaInfo?.url ? ( -
- - {commentMediaInfo?.type === "webpage" ? ( -
- - {thread.thumbnailUrl ? ( +
+
+ Link:  + { + commentMediaInfo?.url.length > 30 ? + commentMediaInfo?.url.slice(0, 30) + "(...)" : + commentMediaInfo?.url + } ({commentMediaInfo?.type === "iframe" ? "video" : commentMediaInfo?.type}) + {isThumbnailClicked[index] ? ( + + -[ + {handleThumbnailClick(index)}}>Close + ] + + ) : null} +
+ {commentMediaInfo?.type === 'iframe' && ( +
+ + {(isThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? ( +