diff --git a/IV-Portal-Franklin/src/App.js b/IV-Portal-Franklin/src/App.js index f743371d666af9778d7935b94ae8a8dd72280b29..3ae2c7f6bf487972ba3c8854ba1c3ad300a41b88 100644 --- a/IV-Portal-Franklin/src/App.js +++ b/IV-Portal-Franklin/src/App.js @@ -19,16 +19,15 @@ import { Stack } from '@mui/material'; import { Chip } from '@mui/material'; import { TextField } from '@mui/material'; -import SovDropZoneWithRead from './components/SovDropZoneWithRead'; import dropimage from "./assets/dropzone.jpg"; -import crypto from "crypto-js"; -import random from "random-string-generator"; import FingerprintIcon from '@mui/icons-material/Fingerprint'; import { Tabs, Tab } from '@mui/material'; -import WarningAmberIcon from '@mui/icons-material/WarningAmber'; +import * as SSIFileServerHelper from 'ssi-lib/FileServerHelper'; + +import { ImageListItem } from '@mui/material'; const universalStore = require('./env/universalObject').certifications; @@ -41,16 +40,6 @@ const Item = styled(Paper)(({ theme }) => ({ color: theme.palette.text.secondary, })); -/* - - todo - - Franklin select images from list rather than uploading - - tidy up the code - see code todo's - -*/ - export default function App() { const api_url = env.REACT_APP_AGENT_API; const hook_url = env.REACT_APP_AGENT_HOOK; @@ -75,6 +64,8 @@ export default function App() { const [value, setValue] = useState("home"); const [verified, setVerified] = useState(false); + const [images, setImages] = useState([]); // image repository list + useEffect(() => { if (value !== "home") return; setImageFile(undefined); @@ -84,33 +75,6 @@ export default function App() { setVerified(false); }, [value]); - useEffect(() => { console.log("invite was set", invite); }, [invite]); - - // *** todo - WE SHOULD SELECT THE IMAGE FROM THE IMAGE REPO, NOT UPLOAD IT ! - // - THERE IS A REPO FUNCTION TO OBTAIN THE HASH TOO, SO NO NEED TO SHA3 IT HERE... - useEffect(() => { - if (imageFile === undefined) return; - - const reader = new FileReader() - - reader.onabort = () => console.log('file reading was aborted') - reader.onerror = () => console.log('file reading has failed') - reader.onload = () => { - let hex = dropImage.split(",")[1]; - let hash = crypto.SHA3(hex); // todo *** get the hash from the image repository - hash = hash.toString(crypto.enc.Hex); - - let filename = random(20) + imageFile.name; - setImageUrl(filename); - - setImageHash(hash); - setDropImage(reader.result); - } - - reader.readAsDataURL(imageFile); - - }, [imageFile]); - useEffect(SSIConnections.startWebSocketEffect(hook_url, setConnectionResults), []); useEffect(SSIConnections.fetchConnectionsEffect(api_url, setConnectionResults), []); @@ -118,8 +82,6 @@ export default function App() { if (connectionResults.length === 0) return; const conn = connectionResults[0]; // todo *** should search presentationResults for a match against INVITATION; don't just get the "latest" one ([0]) - see below also - console.log("conn1", conn); - console.log(invite); switch (conn.rfc23_state) { case "request-received": @@ -128,21 +90,29 @@ export default function App() { break; case "response-sent": - console.log("trust-ping"); + console.log("response-sent"); setTimeout(() => { SSIConnections.trustPing(api_url, conn.connection_id); }, 1000); break; case "completed": - if (!invite.label) return console.log("no invite was found, so the web socket presentation marked as completed was not actioned - it must have been processed earlier."); + if (!invite.label) return; // no invite was found, so the web socket presentation marked as completed was not actioned - it must have been processed earlier console.log("completed"); let body = {}; body.connection_id = conn.connection_id; body.comment = JSON.stringify({ flag: "ImageAuthorshipRequest", - imgHash: imageHash, + imgHash: imageHash, // why do we (i.e. the original webcontroller) send the image hash ??? the holder could use this + // to cheat as this comment appears back at Ruth. this would be ok, say, if the only way we can + // get the fingerprint back on Franklin is via the credential, without relying on what Ruth says... + + // i think the imageHash is sent to Ruth purely for reference purposes because Ruth only + // sends back (presents) "credential -> referent" so presumably Aries uses this reference + // to check the "coment" fields automatically ???? + + // need to look into this more ! }); body.proof_request = { name: "Proof Request" }; body.proof_request.nonce = "1"; // temporary nonce @@ -160,7 +130,6 @@ export default function App() { body.proof_request.version = "1.0"; SSIPresentationsHelper.sendPresentationRequest(api_url, body); - console.log("presentation request was sent"); break; } }, [connectionResults]); @@ -168,7 +137,6 @@ export default function App() { useEffect(() => { if (presentationResults.length === 0) return; const conn = presentationResults[0]; // todo *** should search presentationResults for a match against INVITATION; don't just get the "latest" one ([0]) - see below also - console.log("conn2", conn); switch (conn.state) { case "presentation_received": @@ -194,7 +162,7 @@ export default function App() { return; } - const status = conn.presentation.requested_proof.revealed_attrs.additionalProp1.raw === JSON.parse(conn.presentation_request_dict.comment).imgHash; + const status = conn.presentation.requested_proof.revealed_attrs.additionalProp1.raw === imageHash; setVerified(status ? IMAGE_VERIFIED : IMAGE_NOT_VERIFIED); } @@ -209,55 +177,60 @@ export default function App() { useEffect(SSIPresentationsHelper.startWebSocketEffect(hook_url, setPresentationResults), []); const Interaction = (props) => { - console.log(props.details); - - var flag, geolocation, imgHash, imgTimestamp, imgUrl, macAddress; - var presentation_received, verified; - var proposal_received, offer_sent, request_received, credential_issued, done, issue_credential; + var geolocation, imgHash, imgTimestamp, imgUrl, macAddress; var image_url; var presentation = false; var credential = false; - var attributes; + // *** THE BELOW COMMENTED CODE IS JUST WHAT WAS COPIED OVER FROM A DIFFERENT PORTAL + // WE MAY FIND IT USEFUL AS REFERENCE WHEN UPDATING THIS PORTAL'S VIEW-CREDENTIAL + // CREDIT-CARD COMPONENT LATER - THEN JUST DELETE IT... - // let attributes = props.details.cred_preview.attributes; + // var flag; + // var presentation_received; + // var proposal_received, offer_sent, request_received, credential_issued, done, issue_credential; + // var attributes; - var state = props.details.state; - if (!state) state = "Invalid"; + // // let attributes = props.details.cred_preview.attributes; - const cred_ex_id = props.details.cred_ex_id; - console.log("details", props.details); + // var state = props.details.state; + // if (!state) state = "Invalid"; - if (props.details.presentation_proposal_dict) { // is this a presentation ? + // const cred_ex_id = props.details.cred_ex_id; - attributes = JSON.parse(props.details.presentation_proposal_dict.comment); + // if (props.details.presentation_proposal_dict) { // is this a presentation ? - flag = attributes.flag; - geolocation = attributes.geolocation; - imgHash = attributes.imgHash; - imgTimestamp = attributes.imgTimestamp; - imgUrl = attributes.imgUrl; - macAddress = attributes.macAddress; + // attributes = JSON.parse(props.details.presentation_proposal_dict.comment); - image_url = FILE_SERVER_URL + "uploads/" + imgUrl; + // flag = attributes.flag; + // geolocation = attributes.geolocation; + // imgHash = attributes.imgHash; + // imgTimestamp = attributes.imgTimestamp; + // imgUrl = attributes.imgUrl; + // macAddress = attributes.macAddress; - // presentation states - proposal_received = state === "proposal_received"; // _ NOT - here >>> is this internal to ACA-Py ??? not sure... - presentation_received = state === "presentation_received"; // as above - verified = state === "verified"; - console.log("state", state); + // image_url = FILE_SERVER_URL + "uploads/" + imgUrl; - presentation = true; - } - else { // is this a ... what other states are viewed on the thoday side ? - proposal_received = state === "proposal-received"; - offer_sent = state === "offer-sent"; - request_received = state === "request-received"; - credential_issued = state === "credential-issued"; - done = state === "done"; - - credential = true; - } + // // presentation states + // proposal_received = state === "proposal_received"; // "_" NOT "-" here --> is this internal to ACA-Py ??? not sure... + // presentation_received = state === "presentation_received"; // as above + // verified = state === "verified"; + + // console.log("presentation", props.details); + + // presentation = true; + // } + // else { // is this a ... what other states are viewed on the thoday side ? + // proposal_received = state === "proposal-received"; + // offer_sent = state === "offer-sent"; + // request_received = state === "request-received"; + // credential_issued = state === "credential-issued"; + // done = state === "done"; + + // console.log("other", props.details); + + // credential = true; + // } return ( <Box m={3}> @@ -336,9 +309,10 @@ export default function App() { <Button style={{ marginTop: 40, backgroundColor: "blue" }} variant="contained" - onClick={() => { + onClick={async () => { setValue("details"); createNewInvite(); // create a new invite before switching tabs + setImages(await SSIFileServerHelper.getImagesFromRegistry(FILE_SERVER_URL)); // update the image list }}> CONTINUE </Button> @@ -348,36 +322,58 @@ export default function App() { function renderDetailsTab() { return <div> - <Typography mt={3} style={{ fontWeight: "bold" }}>Enter photograh details</Typography> - <Box - component="form" - sx={{ - '& > :not(style)': { m: 1, width: '95%' }, - }} - noValidate - autoComplete="off" - m={1} - > - <TextField id="standard-basic" label="Name" variant="standard" /> - <TextField id="standard-basic" label="Description" variant="standard" /> - <Stack direction="row" m={1}> - <Box m={2}><WarningAmberIcon style={{ color: "red", fontSize: 50 }} /></Box> - <Box m={2}> - <Typography color="red">Upload is just for development. User should select from image repo list instead...</Typography> - </Box> + <Typography mt={3} style={{ fontWeight: "bold" }}>Select your photograph</Typography> + <Paper style={{ overflow: 'auto' }} elevation={0}> + <Stack direction="row"> + {images.map((image, index) => ( + <ImageListItem key={"image" + index}> + <Box m={1} + sx={{ width: 200 }}> + <img + src={FILE_SERVER_URL + "imageRegistry/" + image} + width="100%" + onClick={async () => { + let hash = (await SSIFileServerHelper.imageHash(FILE_SERVER_URL, image)).data.message; + setImageHash(hash); + setImageFile(image); + }} + /> + </Box> + </ImageListItem> + ))} </Stack> - <SovDropZoneWithRead - dropImage={dropImage} - setDropImage={setDropImage} - setImageFile={setImageFile} - /> - </Box> - { - imageHash && <Box mt={3}> - <Chip style={{ margin: 10, width: 500, backgroundColor: 'transparent' }} icon={<FingerprintIcon fontSize="large" />} label={imageHash} > - </Chip> - </Box> - } + </Paper> + <Typography mt={3} style={{ fontWeight: "bold" }}>Enter photograph details</Typography> + <Grid container> + <Grid xs={3}> + <Box mt={4}> + {imageFile && <Paper style={{ overflow: 'auto', maxWidth: "100%" }} elevation={0}> + <img src={FILE_SERVER_URL + "imageRegistry/" + imageFile} width="100%" /> + </Paper>} + </Box> + </Grid> + <Grid xs={9}> + <Box + component="form" + sx={{ + '& > :not(style)': { m: 1, width: '95%' }, + }} + noValidate + autoComplete="off" + m={1} + > + <TextField id="standard-basic" label="Name" variant="standard" /> + <TextField id="standard-basic" label="Description" variant="standard" /> + </Box> + </Grid> + <Grid xs={12}> + { + imageHash && + <Chip style={{ margin: 10, width: 500, backgroundColor: 'transparent' }} icon={<FingerprintIcon fontSize="large" />} label={imageHash} > + </Chip> + } + </Grid> + </Grid> <Box align="center" m="auto"> <Button style={{ marginTop: 40, backgroundColor: "blue" }} @@ -401,7 +397,7 @@ export default function App() { <CircularProgress /> <Typography ml={2} mt={1}>Awaiting presentation</Typography> </Stack>} - {verified && <Typography ml={2} mt={1}>Your photograph was verified !</Typography>} + {verified && <Typography ml={2} mt={1}>Processing...</Typography>} </Stack> </div>; } @@ -409,7 +405,7 @@ export default function App() { function renderDoneTab() { return <div> {verified === IMAGE_VERIFIED && <Typography mt={3}>Your photograph was successfully verified !</Typography>} - {verified === IMAGE_NOT_VERIFIED && <Typography mt={3}>The image does not match the hash requested by Franklin News</Typography>} + {verified === IMAGE_NOT_VERIFIED && <Typography mt={3}>The presented image hash does not match the hash of the image you selected in Franklin News.</Typography>} {verified === IMAGE_ERROR && <Typography mt={3}>Something went wrong with the verification process, please try again.</Typography>} <Box align="center" m="auto"> <Button diff --git a/IV-Portal-Franklin/src/components/SovQRCodeDrag.js b/IV-Portal-Franklin/src/components/SovQRCodeDrag.js index decf649a269ab70b9405e33758d75f48018fa003..212e0561b1fe73cc4fa53421210b89104a54536c 100644 --- a/IV-Portal-Franklin/src/components/SovQRCodeDrag.js +++ b/IV-Portal-Franklin/src/components/SovQRCodeDrag.js @@ -3,9 +3,7 @@ import QRCode from 'qrcode.react'; export default function QRCodeDrag(props) { function dragStart(e) { - console.log("Drag and drop started:\n\nData: ", props.data) e.dataTransfer.setData("text", JSON.stringify(props.data)); - console.log(JSON.stringify(props.data)); return true; } diff --git a/IV-Portal-Franklin/src/env/env.js b/IV-Portal-Franklin/src/env/env.js index 05aa6315f7f4dca42e888b95b527fc2160f55af6..55a0b60075e86ecd417c6989cba7897eb532f8c1 100644 --- a/IV-Portal-Franklin/src/env/env.js +++ b/IV-Portal-Franklin/src/env/env.js @@ -97,7 +97,5 @@ export default class Env { // ws.close(); // wsCC.close(); - - console.log("Completed"); } }