From 3eabec59d3d3830367fcc32b0c011d6916d3a5ab Mon Sep 17 00:00:00 2001 From: NanBohdanMoroz <92790530+NanSigmaBohdanMoroz@users.noreply.github.com> Date: Tue, 7 Jan 2025 18:18:03 +0200 Subject: [PATCH 1/6] add pcke auth --- package-lock.json | 14 +++++--- package.json | 1 + src/api/apiFactory.js | 60 +++++++++++++++++++++++++++---- src/hooks/useImplicitGrantAuth.js | 23 +++++++----- 4 files changed, 80 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 56c61a6..8849693 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "html2canvas": "^1.4.1", "i18next": "^23.7.18", "i18next-http-backend": "^2.4.2", + "js-sha256": "^0.11.0", "prop-types": "^15.8.1", "react": "^18.2.0", "react-bootstrap": "^2.10.0", @@ -12224,6 +12225,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -17595,16 +17601,16 @@ } }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { diff --git a/package.json b/package.json index 7eca6b0..cc24828 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "html2canvas": "^1.4.1", "i18next": "^23.7.18", "i18next-http-backend": "^2.4.2", + "js-sha256": "^0.11.0", "prop-types": "^15.8.1", "react": "^18.2.0", "react-bootstrap": "^2.10.0", diff --git a/src/api/apiFactory.js b/src/api/apiFactory.js index f79483b..edbe2d1 100644 --- a/src/api/apiFactory.js +++ b/src/api/apiFactory.js @@ -1,6 +1,8 @@ /* eslint-disable no-param-reassign */ +import { sha256 } from "js-sha256"; import { getAuthToken } from "../services/accountRepository"; + const configureInterceptors = (api) => { // Request interceptor for API calls api.interceptors.request.use( @@ -10,7 +12,10 @@ const configureInterceptors = (api) => { "Content-Type": "application/json", }; const accessTokenInfo = getAuthToken(); - config.headers.Authorization = `Bearer ${accessTokenInfo.accessToken}`; + + if (accessTokenInfo?.accessToken) { + config.headers.Authorization = `Bearer ${accessTokenInfo.accessToken}`; + } return config; }, (error) => { @@ -280,10 +285,28 @@ export const createEmbeddedSigningAPI = ( }; }; +export const generateCodeVerifier = () => { + const randomValues = new Uint8Array(32); + window.crypto.getRandomValues(randomValues); + return btoa(String.fromCharCode.apply(null, randomValues)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); +}; + +export const generateCodeChallenge = (codeVerifier) => { + const hash = sha256.arrayBuffer(codeVerifier); + const hashArray = Array.from(new Uint8Array(hash)); + return btoa(String.fromCharCode.apply(null, hashArray)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); +}; + export const createAuthAPI = ( axios, serviceProvider, - implicitGrantPath, + authPath, userInfoPath, eSignBase, scopes, @@ -292,15 +315,21 @@ export const createAuthAPI = ( ) => { const api = createAPI(axios); + const codeVerifier = generateCodeVerifier(); + const codeChallenge = generateCodeChallenge(codeVerifier); + const login = async (nonce, onPopupIsBlocked) => { const url = - `${serviceProvider}${implicitGrantPath}` + - `?response_type=token` + + `${serviceProvider}${authPath}` + + `?response_type=code` + `&scope=${scopes}` + `&client_id=${clientId}` + `&redirect_uri=${returnUrl}` + - `&state=${nonce}`; + `&state=${nonce}` + + `&code_challenge_method=S256` + + `&code_challenge=${codeChallenge}`; const loginWindow = window.open(url, "_blank"); + const newTab = loginWindow; if (!newTab || newTab.closed || typeof newTab.closed === "undefined") { // POPUP BLOCKED @@ -311,6 +340,25 @@ export const createAuthAPI = ( return { window: loginWindow, nonce }; }; + const obtainAccessToken = async (code) => { + const requestData = { + code: `${code}`, + code_verifier: `${codeVerifier}`, + grant_type: "authorization_code", + client_id: `${clientId}`, + code_challenge_method: "S256", + redirect_uri: `${returnUrl}` + + }; + + const response = await api.post( + `${serviceProvider}/oauth/token`, + requestData + ); + + return response.data; + } + const fetchUserInfo = async () => { const response = await api.get(`${serviceProvider}${userInfoPath}`); return response.data; @@ -323,5 +371,5 @@ export const createAuthAPI = ( return response.data.externalAccountId; }; - return { login, fetchUserInfo, fetchExternalAccountId }; + return { login, fetchUserInfo, fetchExternalAccountId, obtainAccessToken }; }; diff --git a/src/hooks/useImplicitGrantAuth.js b/src/hooks/useImplicitGrantAuth.js index bbde545..32db925 100644 --- a/src/hooks/useImplicitGrantAuth.js +++ b/src/hooks/useImplicitGrantAuth.js @@ -66,24 +66,30 @@ export const useImplicitGrantAuth = (onError, onSuccess) => { userEmail: userInfo.email, }); - const parseHash = (hash) => { - const items = hash.split(/=|&/); + const parseSearch = (search) => { + const queryString = search.startsWith('?') ? search.slice(1) : search; + const items = queryString.split(/=|&/); + let i = 0; const response = {}; + while (i + 1 < items.length) { - response[items[i]] = items[i + 1]; + response[decodeURIComponent(items[i])] = decodeURIComponent(items[i + 1]); i += 2; } + return response; }; + - const handleMessage = async (data) => { - // close the browser tab used for OAuth + const handleMessage = async () => { if (loginResult.current.window) { loginResult.current.window.close(); } - const hash = data.hash.substring(1); // remove the # - const response = parseHash(hash); + + const response = parseSearch(loginResult.current.window.location.search); + + const obtainAccessTokenResponse = await authenticationAPI.obtainAccessToken(response.code); const newState = response.state; if (newState !== loginResult.current.nonce) { @@ -93,8 +99,9 @@ export const useImplicitGrantAuth = (onError, onSuccess) => { }); return; } + const accessTokenInfo = { - accessToken: response.access_token, + accessToken: obtainAccessTokenResponse.access_token, accessTokenExpires: new Date(Date.now() + response.expires_in * 1000), }; accountRepository.setAuthToken(accessTokenInfo); From d16efe9e3f1fcd813d926a63e0025ec5a1a8747b Mon Sep 17 00:00:00 2001 From: NanBohdanMoroz <92790530+NanSigmaBohdanMoroz@users.noreply.github.com> Date: Tue, 7 Jan 2025 18:30:49 +0200 Subject: [PATCH 2/6] fix naming --- src/components/header.js | 2 +- src/hooks/{useImplicitGrantAuth.js => usePckeAuth.js} | 0 src/pages/home/index.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/hooks/{useImplicitGrantAuth.js => usePckeAuth.js} (100%) diff --git a/src/components/header.js b/src/components/header.js index d275cd6..c978ef8 100644 --- a/src/components/header.js +++ b/src/components/header.js @@ -5,7 +5,7 @@ import { Navbar } from "react-bootstrap"; import PropTypes from "prop-types"; import logo from "../assets/img/logo.svg"; import * as accountRepository from "../services/accountRepository"; -import { logout } from "../hooks/useImplicitGrantAuth"; +import { logout } from "../hooks/usePckeAuth"; import useBreakpoint, { SIZE_MD, SIZE_SM } from "../hooks/useBreakpoint"; import whiteToggleIcon from "../assets/img/white-links.png"; import blackToggleIcon from "../assets/img/black-links.png"; diff --git a/src/hooks/useImplicitGrantAuth.js b/src/hooks/usePckeAuth.js similarity index 100% rename from src/hooks/useImplicitGrantAuth.js rename to src/hooks/usePckeAuth.js diff --git a/src/pages/home/index.js b/src/pages/home/index.js index f88b01e..7d1c60f 100644 --- a/src/pages/home/index.js +++ b/src/pages/home/index.js @@ -6,7 +6,7 @@ import parse from "html-react-parser"; import { useImplicitGrantAuth, needToLogin -} from "../../hooks/useImplicitGrantAuth"; +} from "../../hooks/usePckeAuth"; import { Card, Layout, WaitingModal, MessageModal } from "../../components"; import { CTASection, TitleSection, ResoursesSection } from "./components"; From 5f77d862871ce8c89d4d739694eeb8929cd3e236 Mon Sep 17 00:00:00 2001 From: NanBohdanMoroz <92790530+NanSigmaBohdanMoroz@users.noreply.github.com> Date: Thu, 9 Jan 2025 23:35:29 +0200 Subject: [PATCH 3/6] add focused view --- public/index.html | 1 + src/api/apiFactory.js | 36 +++-- .../components/focusedView.js | 153 ++++++++++++++++++ .../knowYourCustomer/components/index.js | 1 + src/pages/knowYourCustomer/index.js | 48 ++---- ...dSigningTemplate.js => signingTemplate.js} | 2 +- .../knowYourCustomer/useEmbeddedSigning.js | 4 +- src/pages/sendInsuranceCard/insuranceSlice.js | 22 ++- .../sendInsuranceCard/useGenerateDocument.js | 22 ++- src/services/fileService.js | 2 +- 10 files changed, 223 insertions(+), 68 deletions(-) create mode 100644 src/pages/knowYourCustomer/components/focusedView.js rename src/pages/knowYourCustomer/{embeddedSigningTemplate.js => signingTemplate.js} (98%) diff --git a/public/index.html b/public/index.html index ebdc304..a0a90ff 100644 --- a/public/index.html +++ b/public/index.html @@ -1,6 +1,7 @@ + diff --git a/src/api/apiFactory.js b/src/api/apiFactory.js index edbe2d1..a05c5f7 100644 --- a/src/api/apiFactory.js +++ b/src/api/apiFactory.js @@ -121,18 +121,7 @@ export const createDocumentAPI = ( return response.status; }; - const createEnvelop = async (templateId, signerEmail, signerName) => { - const requestData = { - templateId, - templateRoles: [ - { - email: signerEmail, - name: signerName, - roleName: "signer", - }, - ], - status: "created", - }; + const createEnvelope = async (requestData) => { const response = await api.post( `${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes`, requestData @@ -194,10 +183,10 @@ export const createDocumentAPI = ( createTemplate, addDocumentToTemplate, addTabsToTemplate, - createEnvelop, + createEnvelope, getDocumentId, updateFormFields, - sendEnvelop, + sendEnvelop }; }; @@ -265,6 +254,23 @@ export const createEmbeddedSigningAPI = ( return response.data.url; }; + const embeddedSigningCeremony1 = async (envelopeId, requestData) => { + // const requestData = { + // returnUrl: dsReturnUrl, + // authenticationMethod: "None", + // clientUserId: 1000, + // email: signer.email, + // userName: signer.name, + // }; + + const response = await api.post( + `${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes/${envelopeId}/views/recipient`, + requestData + ); + + return response.data.url; + }; + const embeddedSigning = async (signer, template, onPopupIsBlocked) => { const envelopeId = await createEnvelope(template, signer); const url = await embeddedSigningCeremony(envelopeId, signer); @@ -282,6 +288,8 @@ export const createEmbeddedSigningAPI = ( return { embeddedSigning, + embeddedSigningCeremony, + embeddedSigningCeremony1 }; }; diff --git a/src/pages/knowYourCustomer/components/focusedView.js b/src/pages/knowYourCustomer/components/focusedView.js new file mode 100644 index 0000000..a987578 --- /dev/null +++ b/src/pages/knowYourCustomer/components/focusedView.js @@ -0,0 +1,153 @@ +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { initDocumentAPI, initEmbeddedSigningAPI } from "../../../api"; +import * as accountRepository from "../../../services/accountRepository"; +import { createSigningTemplate } from "../signingTemplate"; + +const createEnvelope = async (envelopeDefinition) => { + const accountInfo = accountRepository.getAccountInfo(); + const api = initDocumentAPI(accountInfo.accountBaseUrl, accountInfo.accountId); + const envelopeId = await api.createEnvelope(envelopeDefinition); + return envelopeId; +}; + +const createRecipientView = async (envelopeId, requestData) => { + const accountInfo = accountRepository.getAccountInfo(); + const api = initEmbeddedSigningAPI(accountInfo.accountBaseUrl, accountInfo.accountId); + const recipientView = await api.embeddedSigningCeremony1(envelopeId, requestData); + return recipientView; +}; + +const createEnvelopDefinition = async (signerEmail, signerName, signerClientId, userPhoto, t) => { + + const template = createSigningTemplate({name: signerName, email: signerEmail, photo: userPhoto}, t); + const document = { + name: process.env.REACT_APP_EMBEDDED_DOCUMENT_NAME, + documentId: 1, + htmlDefinition: { + source: template, + } + } + + const signHere1 = { + anchorString: "/sn1/", + anchorYOffset: "10", + anchorUnits: "pixels", + anchorXOffset: "20", + }; + + const signer1 = { + email: signerEmail, + name: signerName, + clientUserId: signerClientId, + recipientId: 1, + tabs: { + signHereTabs: [signHere1], + }, + }; + + return { + emailSubject: "Please sign this document", + documents: [document], + recipients: { signers: [signer1] }, + status: "sent", + }; +}; + +const createRecipientViewDefinition = (dsReturnUrl, signerEmail, signerName, signerClientId, dsPingUrl) => ({ + returnUrl: `${dsReturnUrl}/know-your-customer?state=123`, + authenticationMethod: "none", + email: signerEmail, + userName: signerName, + clientUserId: signerClientId, + pingFrequency: 600, + pingUrl: dsPingUrl, + frameAncestors: [ window.location.origin, 'https://apps-d.docusign.com', 'https://demo.docusign.net'], + messageOrigins: ['https://apps-d.docusign.com'], +}); + + +// Main Component +export const FocusedView = (args) => { + + const { t } = useTranslation("EmbeddedSigningTemplate"); + + useEffect(() => { + const fetchData = async () => { + try { + const { photo } = args; + + const accountInfo = accountRepository.getAccountInfo(); + + // Create the envelope definition + const envelope = await createEnvelopDefinition(accountInfo.userEmail, accountInfo.userName, 1000, photo, t); + + // Create the envelope + const originEnvelopeId = await createEnvelope(envelope); + + // Create the recipient view definition + const recipientViewDefinition = createRecipientViewDefinition( + window.location.origin, + accountInfo.userEmail, + accountInfo.userName, + 1000, + window.location.origin + ); + + // Create the recipient view + const recipientViewUrl = await createRecipientView(originEnvelopeId, recipientViewDefinition); + + const wdf = process.env.REACT_APP_OAUTH_CLIENT_ID; + console.log(wdf); + // Initialize DocuSign SDK and mount the signing view + const docusign = await window.DocuSign.loadDocuSign(process.env.REACT_APP_OAUTH_CLIENT_ID); + const signing = docusign.signing({ + url: recipientViewUrl, + displayFormat: "focused", + style: { + branding: { + primaryButton: { + backgroundColor: "#333", + color: "#fff", + }, + }, + signingNavigationButton: { + finishText: "You have finished the document! Hooray!", + position: "bottom-center", + }, + }, + }); + console.log(navigator.geolocation); + signing.on("ready", () => { + console.log("UI is rendered"); + }); + + signing.on("sessionEnd", (event) => { + window.location.reload(); + console.log("Session ended:", event); + }); + + signing.mount("#agreement"); + } catch (error) { + console.error("Error fetching recipient view:", error); + } + }; + + fetchData(); + }, []); + + return ( +
+

Signing

+
+
+ ); +}; diff --git a/src/pages/knowYourCustomer/components/index.js b/src/pages/knowYourCustomer/components/index.js index b08628e..84e0287 100644 --- a/src/pages/knowYourCustomer/components/index.js +++ b/src/pages/knowYourCustomer/components/index.js @@ -1,3 +1,4 @@ export * from "./stepDescription"; export * from "./customerInformation"; export * from "./photoModal"; +export * from "./focusedView"; diff --git a/src/pages/knowYourCustomer/index.js b/src/pages/knowYourCustomer/index.js index 4f3c78e..f6a3e94 100644 --- a/src/pages/knowYourCustomer/index.js +++ b/src/pages/knowYourCustomer/index.js @@ -4,14 +4,10 @@ import { useTranslation } from "react-i18next"; import { SeeMore, Layout, - WaitingModal, ErrorModal, MessageModal, } from "../../components"; -import { CustomerInformation, PhotoModal, StepDescription } from "./components"; -import { useEmbeddedSigning } from "./useEmbeddedSigning"; -import * as accountRepository from "../../services/accountRepository"; -import { fileToBase64 } from "../../services/fileService"; +import { CustomerInformation, FocusedView, PhotoModal, StepDescription } from "./components"; import success from "../../assets/img/success.svg"; import warning from "../../assets/img/warning.svg"; import templatePhoto from "../../assets/img/templatePhoto.png"; @@ -25,39 +21,15 @@ const KnowYourCustomer = () => { const [photo, setPhoto] = useState(""); const [simpleFlow, setSimpleFlow] = useState(false); const [lastStep, setLastStep] = useState(false); - const [error, setError] = useState({ title: "", description: "" }); + const [showFocusedView, setShowFocusedView] = useState(false); + const [error] = useState({ title: "", description: "" }); const [showModal, setShowModal] = useState(false); - const [showWaitingModal, setWaitingModal] = useState(false); const [showErrorModal, setShowErrorModal] = useState(false); const [showSuccessModal, setShowSuccessModal] = useState(false); const [showWarningModal, setShowWarningModal] = useState(false); - const [embeddedSigning, inProgress] = useEmbeddedSigning( - accountRepository, - (err) => { - setError(err); - setShowErrorModal(true); - }, - () => { - setLastStep(false); - setPhoto(""); - setShowSuccessModal(true); - } - ); - - useEffect(() => { - if (inProgress) { - setWaitingModal(true); - } else { - setWaitingModal(false); - } - }, [inProgress]); - const handleSigning = async () => { - const signerPhoto = - photo || - `data:image/png;base64, ${await fileToBase64("Photo.png", defaultPhoto)}`; - await embeddedSigning(signerPhoto); + setShowFocusedView(true); }; useEffect(() => { @@ -96,7 +68,14 @@ const KnowYourCustomer = () => {
+ {showFocusedView ? (
+ +
+ ) : + (
{lastStep ? ( { /> )}
+ )} { setLastStep(true); }} /> - showWaitingModal(false)} title={t("WaitingModal.Title")} description={t("WaitingModal.Description")} - /> + /> */} setShowErrorModal(false)} diff --git a/src/pages/knowYourCustomer/embeddedSigningTemplate.js b/src/pages/knowYourCustomer/signingTemplate.js similarity index 98% rename from src/pages/knowYourCustomer/embeddedSigningTemplate.js rename to src/pages/knowYourCustomer/signingTemplate.js index c8b18b2..6fb3521 100644 --- a/src/pages/knowYourCustomer/embeddedSigningTemplate.js +++ b/src/pages/knowYourCustomer/signingTemplate.js @@ -52,7 +52,7 @@ const styles = { }, }; -export const createEmbeddedSigningTemplate = (signer, t) => +export const createSigningTemplate = (signer, t) => renderToString( diff --git a/src/pages/knowYourCustomer/useEmbeddedSigning.js b/src/pages/knowYourCustomer/useEmbeddedSigning.js index 8917b7a..9f881a4 100644 --- a/src/pages/knowYourCustomer/useEmbeddedSigning.js +++ b/src/pages/knowYourCustomer/useEmbeddedSigning.js @@ -1,7 +1,7 @@ import { useEffect, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; import { initEmbeddedSigningAPI } from "../../api"; -import { createEmbeddedSigningTemplate } from "./embeddedSigningTemplate"; +import { createSigningTemplate } from "./signingTemplate"; export const useEmbeddedSigning = (AccountService, onError, onSuccess) => { const { t } = useTranslation("EmbeddedSigningTemplate"); @@ -64,7 +64,7 @@ export const useEmbeddedSigning = (AccountService, onError, onSuccess) => { name: accountInfo.userName, photo, }; - const template = createEmbeddedSigningTemplate(signer, t); + const template = createSigningTemplate(signer, t); try { signingResult.current = await api.embeddedSigning(signer, template, () => diff --git a/src/pages/sendInsuranceCard/insuranceSlice.js b/src/pages/sendInsuranceCard/insuranceSlice.js index 8643324..1141cef 100644 --- a/src/pages/sendInsuranceCard/insuranceSlice.js +++ b/src/pages/sendInsuranceCard/insuranceSlice.js @@ -22,21 +22,27 @@ export const sendInsurance = createAsyncThunk( process.env.REACT_APP_INSURANCE_DOCUMENT_TEMPLATE_NAME, process.env.REACT_APP_INSURANCE_DOCUMENT_TEMPLATE_EMAIL_SUBJECT ); - const documentBase64 = await fileToBase64( - "documentTemplate.docx", - documentTemplate - ); + const documentBase64 = await fileToBase64(documentTemplate); await api.addDocumentToTemplate( templateId, documentBase64, process.env.REACT_APP_INSURANCE_DOCUMENT_NAME ); await api.addTabsToTemplate(templateId); - const envelopId = await api.createEnvelop( + + const requestData = { templateId, - signerEmail, - signerName - ); + templateRoles: [ + { + email: signerEmail, + name: signerName, + roleName: "signer", + }, + ], + status: "created", + }; + + const envelopId = await api.createEnvelop(requestData); const documentId = await api.getDocumentId(envelopId); await api.updateFormFields(documentId, envelopId, insuranceID, insured); await api.sendEnvelop(envelopId); diff --git a/src/pages/sendInsuranceCard/useGenerateDocument.js b/src/pages/sendInsuranceCard/useGenerateDocument.js index fcfb0c3..d32bcb5 100644 --- a/src/pages/sendInsuranceCard/useGenerateDocument.js +++ b/src/pages/sendInsuranceCard/useGenerateDocument.js @@ -49,21 +49,27 @@ export const useGenerateDocument = () => { process.env.REACT_APP_INSURANCE_DOCUMENT_TEMPLATE_NAME, process.env.REACT_APP_INSURANCE_DOCUMENT_TEMPLATE_EMAIL_SUBJECT ); - const documentBase64 = await fileToBase64( - "documentTemplate.docx", - documentTemplate - ); + const documentBase64 = await fileToBase64(documentTemplate); await api.addDocumentToTemplate( templateId, documentBase64, process.env.REACT_APP_INSURANCE_DOCUMENT_NAME ); await api.addTabsToTemplate(templateId); - const envelopId = await api.createEnvelop( + + const requestData = { templateId, - signerEmail, - signerName - ); + templateRoles: [ + { + email: signerEmail, + name: signerName, + roleName: "signer", + }, + ], + status: "created", + }; + + const envelopId = await api.createEnvelop(requestData); const documentId = await api.getDocumentId(envelopId); await api.updateFormFields(documentId, envelopId, insuranceID, insured); await api.sendEnvelop(envelopId); diff --git a/src/services/fileService.js b/src/services/fileService.js index 0f3f2b6..79f3e90 100644 --- a/src/services/fileService.js +++ b/src/services/fileService.js @@ -1,5 +1,5 @@ // Convert file to base64 string -export const fileToBase64 = async (filename, filepath) => { +export const fileToBase64 = async (filepath) => { const response = await fetch(filepath); const fileBlob = await response.blob(); const reader = new FileReader(); From 9cc75363837cc5a94113c0e57eb1195d83a14b71 Mon Sep 17 00:00:00 2001 From: NanBohdanMoroz <92790530+NanSigmaBohdanMoroz@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:51:19 +0200 Subject: [PATCH 4/6] improve style cop --- src/api/apiFactory.js | 88 ++----------------- src/api/index.js | 8 +- .../components/focusedView.js | 21 ++--- .../knowYourCustomer/useEmbeddedSigning.js | 86 ------------------ 4 files changed, 15 insertions(+), 188 deletions(-) delete mode 100644 src/pages/knowYourCustomer/useEmbeddedSigning.js diff --git a/src/api/apiFactory.js b/src/api/apiFactory.js index a05c5f7..f033fd6 100644 --- a/src/api/apiFactory.js +++ b/src/api/apiFactory.js @@ -121,14 +121,6 @@ export const createDocumentAPI = ( return response.status; }; - const createEnvelope = async (requestData) => { - const response = await api.post( - `${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes`, - requestData - ); - return response.data.envelopeId; - }; - const getDocumentId = async (envelopeId) => { const response = await api.get( `${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes/${envelopeId}/docGenFormFields` @@ -190,7 +182,7 @@ export const createDocumentAPI = ( }; }; -export const createEmbeddedSigningAPI = ( +export const createFocusedViewAPI = ( axios, eSignBase, dsReturnUrl, @@ -199,37 +191,7 @@ export const createEmbeddedSigningAPI = ( ) => { const api = createAPI(axios); - const createEnvelope = async (htmlDoc, signer) => { - const requestData = { - emailSubject: - process.env.REACT_APP_EMBEDDED_DOCUMENT_TEMPLATE_EMAIL_SUBJECT, - description: process.env.REACT_APP_EMBEDDED_DOCUMENT_TEMPLATE_DESCRIPTION, - name: process.env.REACT_APP_EMBEDDED_DOCUMENT_TEMPLATE_NAME, - shared: false, - status: "sent", - recipients: { - signers: [ - { - email: signer.email, - name: signer.name, - recipientId: "1", - clientUserId: 1000, - roleName: "signer", - routingOrder: "1", - }, - ], - }, - documents: [ - { - name: process.env.REACT_APP_EMBEDDED_DOCUMENT_NAME, - documentId: 1, - htmlDefinition: { - source: htmlDoc, - }, - }, - ], - }; - + const createEnvelope = async (requestData) => { const response = await api.post( `${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes`, requestData @@ -237,15 +199,8 @@ export const createEmbeddedSigningAPI = ( return response.data.envelopeId; }; - const embeddedSigningCeremony = async (envelopeId, signer) => { - const requestData = { - returnUrl: dsReturnUrl, - authenticationMethod: "None", - clientUserId: 1000, - email: signer.email, - userName: signer.name, - }; + const getRecipientView = async (envelopeId, requestData) => { const response = await api.post( `${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes/${envelopeId}/views/recipient`, requestData @@ -254,42 +209,9 @@ export const createEmbeddedSigningAPI = ( return response.data.url; }; - const embeddedSigningCeremony1 = async (envelopeId, requestData) => { - // const requestData = { - // returnUrl: dsReturnUrl, - // authenticationMethod: "None", - // clientUserId: 1000, - // email: signer.email, - // userName: signer.name, - // }; - - const response = await api.post( - `${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes/${envelopeId}/views/recipient`, - requestData - ); - - return response.data.url; - }; - - const embeddedSigning = async (signer, template, onPopupIsBlocked) => { - const envelopeId = await createEnvelope(template, signer); - const url = await embeddedSigningCeremony(envelopeId, signer); - - const signingWindow = window.open(url, "_blank"); - const newTab = signingWindow; - if (!newTab || newTab.closed || typeof newTab.closed === "undefined") { - // POPUP BLOCKED - onPopupIsBlocked(); - return false; - } - signingWindow.focus(); - return signingWindow; - }; - return { - embeddedSigning, - embeddedSigningCeremony, - embeddedSigningCeremony1 + getRecipientView, + createEnvelope }; }; diff --git a/src/api/index.js b/src/api/index.js index bddde56..6ace734 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -2,7 +2,7 @@ import axios from "axios"; import { createAuthAPI, createDocumentAPI, - createEmbeddedSigningAPI, + createFocusedViewAPI, } from "./apiFactory"; const authenticationAPI = createAuthAPI( @@ -24,8 +24,8 @@ const initDocumentAPI = (baseAccountUrl, accountId) => accountId ); -const initEmbeddedSigningAPI = (baseAccountUrl, accountId) => - createEmbeddedSigningAPI( +const initFocusedViewAPI = (baseAccountUrl, accountId) => + createFocusedViewAPI( axios, process.env.REACT_APP_ESIGN_BASE, process.env.REACT_APP_DS_RETURN_URL, @@ -33,4 +33,4 @@ const initEmbeddedSigningAPI = (baseAccountUrl, accountId) => accountId ); -export { authenticationAPI, initDocumentAPI, initEmbeddedSigningAPI }; +export { authenticationAPI, initDocumentAPI, initFocusedViewAPI }; diff --git a/src/pages/knowYourCustomer/components/focusedView.js b/src/pages/knowYourCustomer/components/focusedView.js index a987578..db9a5ba 100644 --- a/src/pages/knowYourCustomer/components/focusedView.js +++ b/src/pages/knowYourCustomer/components/focusedView.js @@ -1,20 +1,20 @@ import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { initDocumentAPI, initEmbeddedSigningAPI } from "../../../api"; +import { initFocusedViewAPI } from "../../../api"; import * as accountRepository from "../../../services/accountRepository"; import { createSigningTemplate } from "../signingTemplate"; const createEnvelope = async (envelopeDefinition) => { const accountInfo = accountRepository.getAccountInfo(); - const api = initDocumentAPI(accountInfo.accountBaseUrl, accountInfo.accountId); + const api = initFocusedViewAPI(accountInfo.accountBaseUrl, accountInfo.accountId); const envelopeId = await api.createEnvelope(envelopeDefinition); return envelopeId; }; const createRecipientView = async (envelopeId, requestData) => { const accountInfo = accountRepository.getAccountInfo(); - const api = initEmbeddedSigningAPI(accountInfo.accountBaseUrl, accountInfo.accountId); - const recipientView = await api.embeddedSigningCeremony1(envelopeId, requestData); + const api = initFocusedViewAPI(accountInfo.accountBaseUrl, accountInfo.accountId); + const recipientView = await api.getRecipientView(envelopeId, requestData); return recipientView; }; @@ -66,8 +66,6 @@ const createRecipientViewDefinition = (dsReturnUrl, signerEmail, signerName, sig messageOrigins: ['https://apps-d.docusign.com'], }); - -// Main Component export const FocusedView = (args) => { const { t } = useTranslation("EmbeddedSigningTemplate"); @@ -79,13 +77,10 @@ export const FocusedView = (args) => { const accountInfo = accountRepository.getAccountInfo(); - // Create the envelope definition const envelope = await createEnvelopDefinition(accountInfo.userEmail, accountInfo.userName, 1000, photo, t); - // Create the envelope const originEnvelopeId = await createEnvelope(envelope); - // Create the recipient view definition const recipientViewDefinition = createRecipientViewDefinition( window.location.origin, accountInfo.userEmail, @@ -94,12 +89,8 @@ export const FocusedView = (args) => { window.location.origin ); - // Create the recipient view const recipientViewUrl = await createRecipientView(originEnvelopeId, recipientViewDefinition); - const wdf = process.env.REACT_APP_OAUTH_CLIENT_ID; - console.log(wdf); - // Initialize DocuSign SDK and mount the signing view const docusign = await window.DocuSign.loadDocuSign(process.env.REACT_APP_OAUTH_CLIENT_ID); const signing = docusign.signing({ url: recipientViewUrl, @@ -112,12 +103,12 @@ export const FocusedView = (args) => { }, }, signingNavigationButton: { - finishText: "You have finished the document! Hooray!", + finishText: "You have finished the document! Continue!", position: "bottom-center", }, }, }); - console.log(navigator.geolocation); + signing.on("ready", () => { console.log("UI is rendered"); }); diff --git a/src/pages/knowYourCustomer/useEmbeddedSigning.js b/src/pages/knowYourCustomer/useEmbeddedSigning.js deleted file mode 100644 index 9f881a4..0000000 --- a/src/pages/knowYourCustomer/useEmbeddedSigning.js +++ /dev/null @@ -1,86 +0,0 @@ -import { useEffect, useState, useRef } from "react"; -import { useTranslation } from "react-i18next"; -import { initEmbeddedSigningAPI } from "../../api"; -import { createSigningTemplate } from "./signingTemplate"; - -export const useEmbeddedSigning = (AccountService, onError, onSuccess) => { - const { t } = useTranslation("EmbeddedSigningTemplate"); - const [inProgress, setInProgress] = useState(false); - const signingResult = useRef(null); - - const handleError = (error) => { - setInProgress(false); - onError(error); - }; - - const handleSuccess = () => { - setInProgress(false); - onSuccess(); - }; - - const handleMessage = async (data) => { - if (signingResult.current) { - signingResult.current.close(); - } - const { href } = data; - const sections = href.split("?"); - const hasEvents = sections.length === 2; - if (!hasEvents) { - handleError(`Unexpected signing ceremony response: ${data.href}.`); - return; - } - - handleSuccess(); - }; - - const messageListener = async (event) => { - if (!event.data) { - return; - } - const { source } = event.data; - - if (source === "dsResponse") { - await handleMessage(event.data); - } - }; - - useEffect(() => { - window.addEventListener("message", messageListener); - return () => { - window.removeEventListener("message", messageListener); - }; - }, []); - - const embeddedSigning = async (photo) => { - setInProgress(true); - const accountInfo = AccountService.getAccountInfo(); - const api = initEmbeddedSigningAPI( - accountInfo.accountBaseUrl, - accountInfo.accountId - ); - - const signer = { - email: accountInfo.userEmail, - name: accountInfo.userName, - photo, - }; - const template = createSigningTemplate(signer, t); - - try { - signingResult.current = await api.embeddedSigning(signer, template, () => - handleError({ - title: "Popup error", - description: - "Browser popup is blocked. Please allow it and try again", - }) - ); - } catch (e) { - handleError({ - title: "Embedded Signing Error", - description: e.response?.data?.message ?? e.message, - }); - } - }; - - return [embeddedSigning, inProgress]; -}; From 2995c4d2fe4d1f3e7d65a47e63b181d8513feab6 Mon Sep 17 00:00:00 2001 From: NanBohdanMoroz <92790530+NanSigmaBohdanMoroz@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:12:29 +0200 Subject: [PATCH 5/6] update translate --- .../locales/en/EmbeddedSigningTemplate.json | 10 ------- public/locales/en/KnowYourCustomer.json | 10 +++++++ .../components/focusedView.js | 28 +++++++++++-------- src/pages/knowYourCustomer/index.js | 6 ---- src/pages/knowYourCustomer/signingTemplate.js | 16 +++++------ 5 files changed, 35 insertions(+), 35 deletions(-) delete mode 100644 public/locales/en/EmbeddedSigningTemplate.json diff --git a/public/locales/en/EmbeddedSigningTemplate.json b/public/locales/en/EmbeddedSigningTemplate.json deleted file mode 100644 index fd2cacb..0000000 --- a/public/locales/en/EmbeddedSigningTemplate.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Title": "Enter your personal information:", - "FullName": "Full name:", - "StreetAddress": "Street address:", - "City": "City:", - "State": "State:", - "ZipCode": "Zip code:", - "Phone": "Phone:", - "Email": "Email:" -} \ No newline at end of file diff --git a/public/locales/en/KnowYourCustomer.json b/public/locales/en/KnowYourCustomer.json index 352c9d7..aa577d3 100644 --- a/public/locales/en/KnowYourCustomer.json +++ b/public/locales/en/KnowYourCustomer.json @@ -44,5 +44,15 @@ "WarningModal": { "Title": "Alert: Camera not found", "Description": "It appears your device doesn’t have a camera! For demonstration purposes, we’ll provide a generic image." + }, + "FocusedView": { + "Title": "Enter your personal information:", + "FullName": "Full name:", + "StreetAddress": "Street address:", + "City": "City:", + "State": "State:", + "ZipCode": "Zip code:", + "Phone": "Phone:", + "Email": "Email:" } } diff --git a/src/pages/knowYourCustomer/components/focusedView.js b/src/pages/knowYourCustomer/components/focusedView.js index db9a5ba..df53a5f 100644 --- a/src/pages/knowYourCustomer/components/focusedView.js +++ b/src/pages/knowYourCustomer/components/focusedView.js @@ -1,8 +1,9 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { initFocusedViewAPI } from "../../../api"; import * as accountRepository from "../../../services/accountRepository"; import { createSigningTemplate } from "../signingTemplate"; +import { WaitingModal } from "../../../components"; const createEnvelope = async (envelopeDefinition) => { const accountInfo = accountRepository.getAccountInfo(); @@ -18,7 +19,7 @@ const createRecipientView = async (envelopeId, requestData) => { return recipientView; }; -const createEnvelopDefinition = async (signerEmail, signerName, signerClientId, userPhoto, t) => { +const createEnvelopeDefinition = async (signerEmail, signerName, signerClientId, userPhoto, t) => { const template = createSigningTemplate({name: signerName, email: signerEmail, photo: userPhoto}, t); const document = { @@ -55,7 +56,7 @@ const createEnvelopDefinition = async (signerEmail, signerName, signerClientId, }; const createRecipientViewDefinition = (dsReturnUrl, signerEmail, signerName, signerClientId, dsPingUrl) => ({ - returnUrl: `${dsReturnUrl}/know-your-customer?state=123`, + returnUrl: `${dsReturnUrl}`, authenticationMethod: "none", email: signerEmail, userName: signerName, @@ -68,16 +69,19 @@ const createRecipientViewDefinition = (dsReturnUrl, signerEmail, signerName, sig export const FocusedView = (args) => { - const { t } = useTranslation("EmbeddedSigningTemplate"); + const { t } = useTranslation("KnowYourCustomer"); + + const [showWaitingModal, setWaitingModal] = useState(false); useEffect(() => { const fetchData = async () => { try { const { photo } = args; + setWaitingModal(true); const accountInfo = accountRepository.getAccountInfo(); - const envelope = await createEnvelopDefinition(accountInfo.userEmail, accountInfo.userName, 1000, photo, t); + const envelope = await createEnvelopeDefinition(accountInfo.userEmail, accountInfo.userName, 1000, photo, t); const originEnvelopeId = await createEnvelope(envelope); @@ -108,17 +112,13 @@ export const FocusedView = (args) => { }, }, }); - - signing.on("ready", () => { - console.log("UI is rendered"); - }); - signing.on("sessionEnd", (event) => { + signing.on("sessionEnd", () => { window.location.reload(); - console.log("Session ended:", event); }); signing.mount("#agreement"); + setWaitingModal(false); } catch (error) { console.error("Error fetching recipient view:", error); } @@ -129,6 +129,12 @@ export const FocusedView = (args) => { return (
+ showWaitingModal(false)} + title={t("WaitingModal.Title")} + description={t("WaitingModal.Description")} + />

Signing

{ setLastStep(true); }} /> - {/* showWaitingModal(false)} - title={t("WaitingModal.Title")} - description={t("WaitingModal.Description")} - /> */} setShowErrorModal(false)} diff --git a/src/pages/knowYourCustomer/signingTemplate.js b/src/pages/knowYourCustomer/signingTemplate.js index 6fb3521..04f471b 100644 --- a/src/pages/knowYourCustomer/signingTemplate.js +++ b/src/pages/knowYourCustomer/signingTemplate.js @@ -65,12 +65,12 @@ export const createSigningTemplate = (signer, t) => Signer
-

{t("Title")}

+

{t("FocusedView.Title")}

Date: Fri, 10 Jan 2025 13:42:20 +0200 Subject: [PATCH 6/6] Update apiFactory.js --- src/api/apiFactory.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/apiFactory.js b/src/api/apiFactory.js index f033fd6..d83f930 100644 --- a/src/api/apiFactory.js +++ b/src/api/apiFactory.js @@ -175,7 +175,6 @@ export const createDocumentAPI = ( createTemplate, addDocumentToTemplate, addTabsToTemplate, - createEnvelope, getDocumentId, updateFormFields, sendEnvelop