UNPKG

@foreverrbum/ethsign

Version:

This package will allow you to electronically sign documents within your application

426 lines (375 loc) 13 kB
/** * @author pzhu */ import React, { useEffect, useState } from 'react'; import { storeNotif } from './dashboard'; import { useIntl } from 'react-intl'; import * as EmailValidator from 'email-validator'; import { useLocation } from 'react-router'; const endpoint = 'https://auxiliary.ethsign.xyz'; export const EMAIL_NOTIFICATION = 'email-notification'; /** * * @param {function} provider * @returns {string, function} email handleEmail * @description a hook for operation email address */ export const useEmail = (provider) => { const [email, handleEmail] = useState(''); const { pathname } = useLocation(); const [sameUser, handleSameUser] = useSameUser(provider); const { formatMessage } =useIntl(); useEffect(() => { if (!sameUser) { useClearEmailLocalStorage(); handleEmail(''); } if (sameUser) { checkEmail(provider, formatMessage) .then((hasEmail) => { if (hasEmail && pathname === '/manage-profile') { getEmail(provider, formatMessage) .then(({email, rejectSign}) => { handleEmail(email); }); } }); } }, [sameUser, pathname]); return [email, handleEmail]; }; export const useHasEmail = (provider) => { const [hasEmail, handleHasEmail] = useState(false); const { formatMessage } = useIntl(); useEffect(() => { checkEmail(provider, formatMessage) .then((hasEmail) => { handleHasEmail(hasEmail); }) }, [provider]); return { hasEmail }; }; export const useSignNow = () => { const [signNow, handleSignNow] = useState(() => { const { email, hasEmail } = JSON.parse(localStorage.getItem(EMAIL_NOTIFICATION) || '{}'); if ((!hasEmail && !email) || (hasEmail && !email)) { return false; } return true; }); return [signNow, handleSignNow]; }; export const useCheckEmail = (provider, handleOpen) => { const { pathname } = useLocation(); const [sameUser,] = useSameUser(provider); const { formatMessage } = useIntl(); useEffect(()=> { if (!sameUser && pathname !== '/manage-profile') { useClearEmailLocalStorage(); } checkEmail(provider, formatMessage) .then((hasEmail) => { const { skip } = getEmailLocalData(); if (pathname === "/contracts") { if (checkCanPopup(skip, hasEmail)) { handleOpen(true); } else { handleOpen(false); } } }) },[pathname, sameUser]); } /** * * @returns {object, object} ethAddress, ethSignature * @description a hook for operation user information */ export const useUserInfo = (provider) =>{ const [ethAddress, handleEthAddress] = useState(); const [sameUser, handleSameUser] = useSameUser(provider); useEffect(() => { const localData = JSON.parse(localStorage.getItem(EMAIL_NOTIFICATION) || '{}'); if (localData?.ethAddress) { handleEthAddress(localData?.ethAddress); } else { (async ()=> { const signer = provider.getSigner(); const ethAddress = await signer.getAddress(); handleEthAddress(ethAddress); })(provider); } },[sameUser, provider]); return { ethAddress }; }; export const useClearEmailLocalStorage = () => { localStorage.setItem(EMAIL_NOTIFICATION, JSON.stringify({skip: false})); }; /** * * @param {function} provider * @param {function} formatMessage i18n * @returns {Promise} email info * @description for get email info */ export const getEmail = async (provider, formatMessage) =>{ // get data from local storage const {email, timeStamp, ethAddress, rejectSign} = getEmailLocalData(); // check timeStamp is expired // if not isExpired and the same user , return the local data // else return the backend data const sameUser = await isSameUser(provider, ethAddress); if (!isExpired(timeStamp, 24) && sameUser && !rejectSign) { return Promise.resolve({ email, rejectSign}); } else { const {ethAddress, ethSignature, rejectSign} = await getSignerInfo(provider, formatMessage); if (rejectSign) { setEmailLocalData({skip: false, email: ''}) return Promise.resolve({email: '', rejectSign}); } else { return await fetch(`${endpoint}/getEmail`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ethAddress, ethSignature }) }) .then(res => res.json()) .then(({email}) => { setEmailLocalData({timeStamp: new Date().getTime(), email}) return { email, rejectSign }; }) .catch(error => { storeNotif(formatMessage({id: 'BAD_REQUEST'}), formatMessage({id: 'REQUEST_EMAIL_ADDRESS_FAILED'}), 'danger'); }); } } } /** * * @param {function} provider * @param {string} email * @param {function} formatMessage i18n * @returns {Promise} save result * @description a function for save email */ export const saveEmail = async (provider, email, formatMessage) => { // if email is empty string "", it is to show customer want to delete email if (email) { //validate email address const validateResult = EmailValidator.validate(email); if (!validateResult) { storeNotif(formatMessage({id: 'INVALID_EMAIL'}), formatMessage({id: 'INVALID_EMAIL_MESSAGE'}), 'danger'); return Promise.resolve({success: false}); } } else if (email === '') { email = null; } const {ethAddress, ethSignature} = await getSignerInfo(provider, formatMessage); return await fetch(`${endpoint}/setEmail`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ethAddress, ethSignature, email }) }) .then(res => res.json()) .then(data => { // update hasEmial value when save email success let hasEmail = true; if (email === null) { hasEmail = false; } // update email address setEmailLocalData({hasEmail, email}) storeNotif(formatMessage({id: 'SAVE_SUCCESS'}), formatMessage({id: 'EMAIL_SAVE_SUCCESS'}), 'success'); return data; }) .catch(error => { storeNotif(formatMessage({id: 'BAD_REQUEST'}), formatMessage({id: 'REQUEST_EMAIL_ADDRESS_FAILED'}), 'danger'); return Promise.resolve({success: false}); }); } export const checkEmail = async (provider, formatMessage) => { const { hasEmail, ethAddress, timeStamp } = JSON.parse(localStorage.getItem(EMAIL_NOTIFICATION) || '{}'); const sameUser = await isSameUser(provider, ethAddress); if (!isExpired(timeStamp, 24) && sameUser) { return Promise.resolve(hasEmail) } else { const ethAddress = await getEthAddress(provider); if(!ethAddress) { // Return true here to ensure the "Welcome to EthSign" popup does not show. // We want to display an error, not a welcome message. return true; } return await fetch(`${endpoint}/checkEmail`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ethAddresses: [ethAddress] }) }) .then(res => { return res.json() }) .then(res => { let hasEmail = false; const findItem = res.find(r => { return Object.keys(r)[0] === ethAddress; }); if (findItem) { hasEmail = Object.values(findItem)[0]; // update hasEmail on localstorage setEmailLocalData({hasEmail}); } return hasEmail; }) .catch(e => { storeNotif(formatMessage({id: 'BAD_REQUEST'}), formatMessage({id: 'CHECK_EMAIL_ADDRESS_FAILED'}, {error: e.toString()}), 'danger') }); } } export const getEthAddress = async (provider) => { const signer = provider.getSigner(); try { return await signer.getAddress(); } catch(err) { // getAddress returned an error (user logged out) return null; } } /** * * @param {function} provider * @param {function} formatMessage i18n * @returns {object, object} ethAddress, ethSignature */ const getSignerInfo = async (provider, formatMessage) => { let rejectSign = false; // get info from local first let localData = getEmailLocalData(); let message = localData?.message; const timeStamp = localData?.timeStamp; let ethAddress = localData?.ethAddress; let ethSignature = localData?.ethSignature; // check timeStamp is expired // if isExpired (24 h) and rpc user is not equal the local user, request data from backend // else response directly local data const sameUser = await isSameUser(provider, ethAddress); if (isExpired(timeStamp, 24) || !ethSignature) { // request data from backend const resData = await getSignMessage(formatMessage); message = resData?.message; const signer = provider.getSigner(); ethAddress = (await signer.getAddress()); try { ethSignature = await signer.signMessage(message); } catch (e) { rejectSign = true; ethSignature = undefined; message = undefined; } setEmailLocalData({timeStamp: new Date().getTime(), message, ethAddress, ethSignature, rejectSign}) } return {ethAddress, ethSignature, rejectSign} } /** * * @param {function} provider * @param {String} ethAddressFromLocal * @returns {boolean} * @description check the ethAddressFromLocal is equal with ethAddressFromRpc */ const isSameUser = async (provider, ethAddressFromLocal) => { try { // if the ethAddressFromLocal equal undefined, it is to show this is a new user, default the new user is a sigle user, should return true const signer = provider.getSigner(); const ethAddressFromRpc = await signer.getAddress(); if (ethAddressFromLocal === undefined) { setEmailLocalData({ethAddress:ethAddressFromRpc}) return true }; return ethAddressFromLocal === ethAddressFromRpc; } catch (err) { // getAddress threw an error return false; } } /** * * @param {function} formatMessage i18n * @returns {Promise} validate message */ const getSignMessage = async (formatMessage) => { return await fetch(`${endpoint}/getMessage`) .then(res => res.json()) .then(res => { return res }) .catch(error => { storeNotif(formatMessage({id: 'REQUEST_MESSAGE_FAILDED'}), formatMessage({id: 'REQUEST_EMAIL_ADDRESS_FAILED'}), 'danger') }) } /** * * @param {millisecond} time * @param {hours} expiredTime * @returns {boolean} */ const isExpired = (time = 0, expiredTime) => { const currentTime = new Date().getTime(); return (currentTime - time) /(1000*60*60) > expiredTime; } /** * * @param {*} provider * @returns [boolean, function] * @description check the user have change */ export const useSameUser = (provider) => { const [sameUser, handleSameUser] = useState(true); useEffect(()=> { (async () =>{ const localData = JSON.parse(localStorage.getItem(EMAIL_NOTIFICATION) || '{}'); handleSameUser(await isSameUser(provider, localData?.ethAddress)) })(); }); return [sameUser, handleSameUser]; } /** * * @param {boolean} skip * @param {boolean} hasEmail the use have email set before * @returns {boolean} decide popup the email notification */ export const checkCanPopup = (skip, hasEmail) => { if (!skip && !hasEmail) { return true; } return false; } /** * * @returns the local storage data about EMAIL_NOTIFICATION */ export const getEmailLocalData = () => { return JSON.parse(localStorage.getItem(EMAIL_NOTIFICATION) || '{}') } /** * * @param {Object} value * @description update the local storage data about EMAIL_NOTIFICATION */ export const setEmailLocalData = (value) => { const localData = getEmailLocalData(); localStorage.setItem(EMAIL_NOTIFICATION, JSON.stringify({...localData, ...value})); }