import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import { MyContext, getBaseApiReact } from "../../App"; import { Card, CardContent, CardHeader, Typography, RadioGroup, Radio, FormControlLabel, Button, Box, ButtonBase, Divider, Dialog, IconButton, } from "@mui/material"; import { getNameInfo } from "../Group/Group"; import { getFee } from "../../background"; import { Spacer } from "../../common/Spacer"; import { CustomizedSnackbars } from "../Snackbar/Snackbar"; import RefreshIcon from "@mui/icons-material/Refresh"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import { extractComponents } from "../Chat/MessageDisplay"; import { executeEvent } from "../../utils/events"; import { CustomLoader } from "../../common/CustomLoader"; import PollIcon from "@mui/icons-material/Poll"; import ImageIcon from "@mui/icons-material/Image"; import CloseIcon from "@mui/icons-material/Close"; function decodeHTMLEntities(str) { const txt = document.createElement("textarea"); txt.innerHTML = str; return txt.value; } const parseQortalLink = (link) => { const prefix = "qortal://use-embed/"; if (!link.startsWith(prefix)) { throw new Error("Invalid link format"); } // Decode any HTML entities in the link link = decodeHTMLEntities(link); // Separate the type and query string const [typePart, queryPart] = link.slice(prefix.length).split("?"); // Ensure only the type is parsed const type = typePart.split("/")[0].toUpperCase(); const params = {}; if (queryPart) { const queryPairs = queryPart.split("&"); queryPairs.forEach((pair) => { const [key, value] = pair.split("="); if (key && value) { const decodedKey = decodeURIComponent(key.trim()); const decodedValue = value.trim().replace( /<\/?[^>]+(>|$)/g, "" // Remove any HTML tags ); params[decodedKey] = decodedValue; } }); } return { type, ...params }; }; const getPoll = async (name) => { const pollName = name; const url = `${getBaseApiReact()}/polls/${pollName}`; const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", }, }); const responseData = await response.json(); if (responseData?.message?.includes("POLL_NO_EXISTS")) { throw new Error("POLL_NO_EXISTS"); } else if (responseData?.pollName) { const urlVotes = `${getBaseApiReact()}/polls/votes/${pollName}`; const responseVotes = await fetch(urlVotes, { method: "GET", headers: { "Content-Type": "application/json", }, }); const responseDataVotes = await responseVotes.json(); return { info: responseData, votes: responseDataVotes, }; } }; export const Embed = ({ embedLink }) => { const [errorMsg, setErrorMsg] = useState(""); const [isLoading, setIsLoading] = useState(false); const [poll, setPoll] = useState(null); const [type, setType] = useState(""); const hasFetched = useRef(false); const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const [external, setExternal] = useState(null); const [imageUrl, setImageUrl] = useState(""); const [parsedData, setParsedData] = useState(null) const handlePoll = async (parsedData) => { try { setIsLoading(true); setErrorMsg(""); setType("POLL"); if (!parsedData?.name) throw new Error("Invalid poll embed link. Missing name."); const pollRes = await getPoll(parsedData.name); setPoll(pollRes); if (parsedData?.ref) { const res = extractComponents(decodeURIComponent(parsedData.ref)); if (res?.service && res?.name) { setExternal(res); } } } catch (error) { setErrorMsg(error?.message || "Invalid embed link"); } finally { setIsLoading(false); } }; const getImage = async ({ identifier, name, service }) => { try { let numberOfTries = 0; let imageFinalUrl = null; const tryToGetImageStatus = async () => { const urlStatus = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}?build=true`; const responseStatus = await fetch(urlStatus, { method: "GET", headers: { "Content-Type": "application/json", }, }); const responseData = await responseStatus.json(); if (responseData?.status === "READY") { imageFinalUrl = `${getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}?async=true`; // If parsedData is used here, it must be defined somewhere if (parsedData?.ref) { const res = extractComponents(decodeURIComponent(parsedData.ref)); if (res?.service && res?.name) { setExternal(res); } } } }; // Retry logic while (!imageFinalUrl && numberOfTries < 3) { await tryToGetImageStatus(); if (!imageFinalUrl) { numberOfTries++; await new Promise((res)=> { setTimeout(()=> { res(null) }, 5000) }) } } if (imageFinalUrl) { return imageFinalUrl; } else { setErrorMsg( "Unable to download IMAGE. Please try again later by clicking the refresh button" ); return null; } } catch (error) { console.error("Error fetching image:", error); setErrorMsg( "An unexpected error occurred while trying to download the image" ); return null; } }; const handleImage = async (parsedData) => { try { setIsLoading(true); setErrorMsg(""); if (!parsedData?.name || !parsedData?.service || !parsedData?.identifier) throw new Error("Invalid image embed link. Missing param."); const image = await getImage({ name: parsedData.name, service: parsedData.service, identifier: parsedData?.identifier, }); setImageUrl(image); } catch (error) { setErrorMsg(error?.message || "Invalid embed link"); } finally { setIsLoading(false); } }; const handleLink = () => { try { const parsedData = parseQortalLink(embedLink); setParsedData(parsedData) const type = parsedData?.type; switch (type) { case "POLL": { handlePoll(parsedData); } break; case "IMAGE": setType("IMAGE"); break; default: break; } } catch (error) { setErrorMsg(error?.message || "Invalid embed link"); } }; const fetchImage = () => { try { const parsedData = parseQortalLink(embedLink); handleImage(parsedData); } catch (error) { setErrorMsg(error?.message || "Invalid embed link"); } }; const openExternal = () => { executeEvent("addTab", { data: external }); executeEvent("open-apps-mode", {}); }; useEffect(() => { if (!embedLink || hasFetched.current) return; handleLink(); hasFetched.current = true; }, [embedLink]); return (