diff --git a/apps/OpenSign/src/pages/GuestLogin.js b/apps/OpenSign/src/pages/GuestLogin.js index 6647e1ee5..aad55b42a 100644 --- a/apps/OpenSign/src/pages/GuestLogin.js +++ b/apps/OpenSign/src/pages/GuestLogin.js @@ -169,10 +169,10 @@ function GuestLogin() { e.preventDefault(); const params = { ...contact, docId: documentId }; try { + setLoading(true); const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params); // console.log("linkContactRes ", linkContactRes); setContactId(linkContactRes.contactId); - setLoading(true); setEnterOtp(true); await SendOtp(); } catch (err) { diff --git a/apps/OpenSign/src/pages/PdfRequestFiles.js b/apps/OpenSign/src/pages/PdfRequestFiles.js index a1387a5f2..0cc5fb9c2 100644 --- a/apps/OpenSign/src/pages/PdfRequestFiles.js +++ b/apps/OpenSign/src/pages/PdfRequestFiles.js @@ -274,6 +274,10 @@ function PdfRequestFiles() { }; setAlreadySign(true); setIsCompleted(data); + setIsCelebration(true); + setTimeout(() => { + setIsCelebration(false); + }, 5000); } else if (declined) { const currentDecline = { currnt: "another", @@ -286,6 +290,13 @@ function PdfRequestFiles() { ); setIsExpired(true); setExpiredDate(expireDateFormat); + } // Check if the current signer is not a last signer and handle the complete message. + else if (isNextUser) { + setIsCompleted({ + isModal: true, + message: + "You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers." + }); } const isGuestSign = location.pathname.includes("/load/"); @@ -315,164 +326,157 @@ function PdfRequestFiles() { } } } - if (documentData.length > 0) { - const checkDocIdExist = - documentData[0].AuditTrail && - documentData[0].AuditTrail.length > 0 && - documentData[0].AuditTrail.filter( - (data) => data.Activity === "Signed" - ); + const checkDocIdExist = + documentData[0].AuditTrail && + documentData[0].AuditTrail.length > 0 && + documentData[0].AuditTrail.filter((data) => data.Activity === "Signed"); - const checkAlreadySign = - documentData[0].AuditTrail && - documentData[0].AuditTrail.length > 0 && - documentData[0].AuditTrail.filter( - (data) => - data.UserPtr.objectId === currUserId && data.Activity === "Signed" - ); + const checkAlreadySign = + documentData[0].AuditTrail && + documentData[0].AuditTrail.length > 0 && + documentData[0].AuditTrail.filter( + (data) => + data.UserPtr.objectId === currUserId && data.Activity === "Signed" + ); + if ( + checkAlreadySign && + checkAlreadySign[0] && + checkAlreadySign.length > 0 + ) { + setAlreadySign(true); + } else { + const obj = documentData?.[0]; + setSendInOrder(obj?.SendinOrder || false); if ( - checkAlreadySign && - checkAlreadySign[0] && - checkAlreadySign.length > 0 + obj && + obj.Signers && + obj.Signers.length > 0 && + obj.Placeholders && + obj.Placeholders.length > 0 ) { - setAlreadySign(true); - } else { - const obj = documentData?.[0]; - setSendInOrder(obj?.SendinOrder || false); - if ( - obj && - obj.Signers && - obj.Signers.length > 0 && - obj.Placeholders && - obj.Placeholders.length > 0 - ) { - const params = { - event: "viewed", - contactId: currUserId, - body: { - objectId: documentData?.[0].objectId, - file: documentData?.[0]?.SignedUrl || documentData?.[0]?.URL, - name: documentData?.[0].Name, - note: documentData?.[0].Note || "", - description: documentData?.[0].Description || "", - signers: documentData?.[0].Signers?.map((x) => ({ - name: x?.Name, - email: x?.Email, - phone: x?.Phone - })), - viewedBy: jsonSender.email, - viewedAt: new Date(), - createdAt: documentData?.[0].createdAt - } - }; + const params = { + event: "viewed", + contactId: currUserId, + body: { + objectId: documentData?.[0].objectId, + file: documentData?.[0]?.SignedUrl || documentData?.[0]?.URL, + name: documentData?.[0].Name, + note: documentData?.[0].Note || "", + description: documentData?.[0].Description || "", + signers: documentData?.[0].Signers?.map((x) => ({ + name: x?.Name, + email: x?.Email, + phone: x?.Phone + })), + viewedBy: jsonSender.email, + viewedAt: new Date(), + createdAt: documentData?.[0].createdAt + } + }; - try { - await axios.post( - `${localStorage.getItem("baseUrl")}functions/callwebhook`, - params, - { - headers: { - "Content-Type": "application/json", - "X-Parse-Application-Id": - localStorage.getItem("parseAppId"), - sessiontoken: localStorage.getItem("accesstoken") - } + try { + await axios.post( + `${localStorage.getItem("baseUrl")}functions/callwebhook`, + params, + { + headers: { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId"), + sessiontoken: localStorage.getItem("accesstoken") } - ); - } catch (err) { - console.log("Err ", err); - } - } - } - - let signers = []; - let unSignedSigner = []; - - //check document is signed or not - if (checkDocIdExist && checkDocIdExist.length > 0) { - setIsDocId(true); - const signerRes = documentData[0].Signers; - //comparison auditTrail user details with signers user details - for (let i = 0; i < signerRes.length; i++) { - const signerId = signerRes[i].objectId; - - let isSignedSignature = false; - for (let j = 0; j < checkDocIdExist.length; j++) { - const signedExist = - checkDocIdExist[j] && checkDocIdExist[j].UserPtr.objectId; - //checking signerObjId and auditTrail User objId - // if match then add signed data in signer array and break loop - - if (signerId === signedExist) { - signers.push({ ...signerRes[i], ...signerRes[i] }); - isSignedSignature = true; - break; } - // if does not match then add unsigned data in unSignedSigner array - } - if (!isSignedSignature) { - unSignedSigner.push({ ...signerRes[i], ...signerRes[i] }); - } + ); + } catch (err) { + console.log("Err ", err); } - setSignedSigners(signers); - setUnSignedSigners(unSignedSigner); + } + } - setSignerPos(documentData[0].Placeholders); - } else { - let unsigned = []; - for (let i = 0; i < documentData.length; i++) { - unsigned.push(documentData[i].Signers); + let signers = []; + let unSignedSigner = []; + + //check document is signed or not + if (checkDocIdExist && checkDocIdExist.length > 0) { + setIsDocId(true); + const signerRes = documentData[0].Signers; + //comparison auditTrail user details with signers user details + for (let i = 0; i < signerRes.length; i++) { + const signerId = signerRes[i].objectId; + + let isSignedSignature = false; + for (let j = 0; j < checkDocIdExist.length; j++) { + const signedExist = + checkDocIdExist[j] && checkDocIdExist[j].UserPtr.objectId; + //checking signerObjId and auditTrail User objId + // if match then add signed data in signer array and break loop + + if (signerId === signedExist) { + signers.push({ ...signerRes[i], ...signerRes[i] }); + isSignedSignature = true; + break; + } + // if does not match then add unsigned data in unSignedSigner array } - setUnSignedSigners(unsigned[0]); - setSignerPos(documentData[0].Placeholders); - } - setPdfDetails(documentData); - // Check if the current signer is not a last signer and handle the complete message. - if (isNextUser) { - const getSignedAuditTrail = documentData[0].AuditTrail.filter( - (data) => data.Activity === "Signed" - ); - const isLastSigner = - getSignedAuditTrail?.length === documentData?.[0]?.Signers?.length; - if (!isLastSigner) { - setIsCompleted({ - isModal: true, - message: - "You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers." - }); - } else { - setIsCelebration(true); - setTimeout(() => { - setIsCelebration(false); - }, 5000); + if (!isSignedSignature) { + unSignedSigner.push({ ...signerRes[i], ...signerRes[i] }); } } - //checking if condition current user already sign or owner does not exist as a signer or document has been declined by someone or document has been expired - //then stop to display tour message - if ( - (checkAlreadySign && - checkAlreadySign[0] && - checkAlreadySign.length > 0) || - !currUserId || - declined || - currDate > expireUpdateDate - ) { - setRequestSignTour(false); - } else { - //else condition to check current user exist in contracts_Users class and check tour message status - //if not then check user exist in contracts_Contactbook class and check tour message status - const localuser = localStorage.getItem( - `Parse/${appInfo.appId}/currentUser` - ); - const currentUser = JSON.parse(JSON.stringify(localuser)); - const currentUserEmail = currentUser.email; - const res = await contractUsers(currentUserEmail); + setSignedSigners(signers); + setUnSignedSigners(unSignedSigner); + setSignerPos(documentData[0].Placeholders); + } else { + let unsigned = []; + for (let i = 0; i < documentData.length; i++) { + unsigned.push(documentData[i].Signers); + } + setUnSignedSigners(unsigned[0]); + setSignerPos(documentData[0].Placeholders); + } + setPdfDetails(documentData); + //checking if condition current user already sign or owner does not exist as a signer or document has been declined by someone or document has been expired + //then stop to display tour message + if ( + (checkAlreadySign && + checkAlreadySign[0] && + checkAlreadySign.length > 0) || + !currUserId || + declined || + currDate > expireUpdateDate + ) { + setRequestSignTour(false); + } else { + //else condition to check current user exist in contracts_Users class and check tour message status + //if not then check user exist in contracts_Contactbook class and check tour message status + const localuser = localStorage.getItem( + `Parse/${appInfo.appId}/currentUser` + ); + const currentUser = JSON.parse(JSON.stringify(localuser)); + const currentUserEmail = currentUser.email; + const res = await contractUsers(currentUserEmail); + if (res === "Error: Something went wrong!") { + setHandleError("Error: Something went wrong!"); + } else if (res[0] && res?.length) { + setContractName("_Users"); + currUserId = res[0].objectId; + setSignerUserId(currUserId); + const tourstatus = res[0].TourStatus && res[0].TourStatus; + if (tourstatus && tourstatus.length > 0) { + setTourStatus(tourstatus); + const checkTourRequestSign = tourstatus.filter( + (data) => data.requestSign + ); + if (checkTourRequestSign && checkTourRequestSign.length > 0) { + setRequestSignTour(checkTourRequestSign[0].requestSign); + } + } + } else if (res?.length === 0) { + const res = await contactBook(currUserId); if (res === "Error: Something went wrong!") { setHandleError("Error: Something went wrong!"); - } else if (res[0] && res?.length) { - setContractName("_Users"); - currUserId = res[0].objectId; - setSignerUserId(currUserId); + } else if (res[0] && res.length) { + setContractName("_Contactbook"); + const objectId = res[0].objectId; + setSignerUserId(objectId); const tourstatus = res[0].TourStatus && res[0].TourStatus; if (tourstatus && tourstatus.length > 0) { setTourStatus(tourstatus); @@ -483,33 +487,12 @@ function PdfRequestFiles() { setRequestSignTour(checkTourRequestSign[0].requestSign); } } - } else if (res?.length === 0) { - const res = await contactBook(currUserId); - if (res === "Error: Something went wrong!") { - setHandleError("Error: Something went wrong!"); - } else if (res[0] && res.length) { - setContractName("_Contactbook"); - const objectId = res[0].objectId; - setSignerUserId(objectId); - const tourstatus = res[0].TourStatus && res[0].TourStatus; - if (tourstatus && tourstatus.length > 0) { - setTourStatus(tourstatus); - const checkTourRequestSign = tourstatus.filter( - (data) => data.requestSign - ); - if (checkTourRequestSign && checkTourRequestSign.length > 0) { - setRequestSignTour(checkTourRequestSign[0].requestSign); - } - } - } else if (res.length === 0) { - setHandleError("Error: User does not exist!"); - } + } else if (res.length === 0) { + setHandleError("Error: User does not exist!"); } } - setIsUiLoading(false); - } else { - alert("No data found!"); } + setIsUiLoading(false); } else if ( documentData === "Error: Something went wrong!" || (documentData.result && documentData.result.error) @@ -824,7 +807,7 @@ function PdfRequestFiles() { "https://qikinnovation.ams3.digitaloceanspaces.com/logo.png"; let url = `${localStorage.getItem( "baseUrl" - )}functions/sendmailv3/`; + )}functions/sendmailv3`; const headers = { "Content-Type": "application/json", "X-Parse-Application-Id": diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index 32a047a3f..0552d2930 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -1,4 +1,4 @@ -import PDF from './parsefunction/pdf/PDF.min.js'; +import PDF from './parsefunction/pdf/PDF.js'; import sendmailv3 from './parsefunction/sendMailv3.js'; import GoogleSign from './parsefunction/GoogleSign.js'; import ZohoDetails from './parsefunction/ZohoDetails.js'; diff --git a/apps/OpenSignServer/cloud/parsefunction/pdf/PDF.js b/apps/OpenSignServer/cloud/parsefunction/pdf/PDF.js new file mode 100644 index 000000000..86d5257e8 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/pdf/PDF.js @@ -0,0 +1,473 @@ +import fs from 'node:fs'; +import axios from 'axios'; +import { SignPdf } from '@signpdf/signpdf'; +import { P12Signer } from '@signpdf/signer-p12'; +import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib'; +import { PDFDocument } from 'pdf-lib'; +import { replaceMailVaribles, saveFileUsage } from '../../../Utils.js'; +import GenerateCertificate from './GenerateCertificate.js'; +const serverUrl = process.env.SERVER_URL; +const APPID = process.env.APP_ID; +const masterKEY = process.env.MASTER_KEY; +const eSignName = 'opensign'; +const eSigncontact = 'hello@opensignlabs.com'; +// `updateDoc` is used to create url in from pdfFile +async function uploadFile(pdfName, filepath) { + try { + const filedata = fs.readFileSync(filepath); + const file = new Parse.File(pdfName, [...filedata], 'application/pdf'); + await file.save({ useMasterKey: true }); + const fileUrl = file.url(); + return { imageUrl: fileUrl }; + } catch (err) { + console.log('Err ', err); + // `fs.unlinkSync` is used to remove exported signed pdf file from exports folder + fs.unlinkSync(filepath); + } +} + +// `updateDoc` is used to update signedUrl, AuditTrail, Iscompleted in document +async function updateDoc(docId, url, userId, ipAddress, data, className, sign) { + try { + const UserPtr = { + __type: 'Pointer', + className: className, + objectId: userId, + }; + const obj = { + UserPtr: UserPtr, + SignedUrl: url, + Activity: 'Signed', + ipAddress: ipAddress, + SignedOn: new Date(), + Signature: sign, + }; + let updateAuditTrail; + if (data.AuditTrail && data.AuditTrail.length > 0) { + const AuditTrail = JSON.parse(JSON.stringify(data.AuditTrail)); + const existingIndex = AuditTrail.findIndex( + entry => entry.UserPtr.objectId === userId && entry.Activity !== 'Created' + ); + existingIndex !== -1 + ? (AuditTrail[existingIndex] = { ...AuditTrail[existingIndex], ...obj }) + : AuditTrail.push(obj); + + updateAuditTrail = AuditTrail; + } else { + updateAuditTrail = [obj]; + } + + const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed'); + let isCompleted = false; + if (data.Signers && data.Signers.length > 0) { + if (auditTrail.length === data.Placeholders.length) { + isCompleted = true; + } + } else { + isCompleted = true; + } + const body = { + SignedUrl: url, + AuditTrail: updateAuditTrail, + IsCompleted: isCompleted, + }; + const signedRes = await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, { + headers: { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': APPID, + 'X-Parse-Master-Key': masterKEY, + }, + }); + return { isCompleted: isCompleted, message: 'success', AuditTrail: updateAuditTrail }; + } catch (err) { + console.log('update doc err ', err); + return 'err'; + } +} + +// `sendCompletedMail` is used to send copy of completed document mail +async function sendCompletedMail(obj) { + const url = obj.url; + const doc = obj.doc; + const sender = obj.doc.ExtUserPtr; + const pdfName = doc.Name; + const mailLogo = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; + const recipient = sender.Email; + let subject = `Document "${pdfName}" has been signed by all parties`; + let body = + "

Document signed successfully

All parties have successfully signed the document " + + `"${pdfName}"` + + '. Kindly download the document from the attachment.

This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' + + sender.Email + + ' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ here.

'; + + if (obj?.isCustomMail) { + try { + const tenantCreditsQuery = new Parse.Query('partners_Tenant'); + tenantCreditsQuery.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: sender.UserId.objectId, + }); + const res = await tenantCreditsQuery.first(); + if (res) { + const _res = JSON.parse(JSON.stringify(res)); + if (_res?.CompletionSubject) { + subject = _res?.CompletionSubject; + } + if (_res?.CompletionBody) { + body = _res?.CompletionBody; + } + const expireDate = doc.ExpiryDate.iso; + const newDate = new Date(expireDate); + const localExpireDate = newDate.toLocaleDateString('en-US', { + day: 'numeric', + month: 'long', + year: 'numeric', + }); + + const variables = { + document_title: pdfName, + sender_name: sender.Name, + sender_mail: sender.Email, + sender_phone: sender.Phone, + receiver_name: sender.Name, + receiver_email: sender.Email, + receiver_phone: sender.Phone, + expiry_date: localExpireDate, + company_name: sender.Company, + }; + const replaceVar = replaceMailVaribles(subject, body, variables); + subject = replaceVar.subject; + body = replaceVar.body; + } + } catch (err) { + console.log('error in fetch tenant in signpdf', err.message); + } + } + const params = { + extUserId: sender.objectId, + url: url, + from: 'OpenSign™', + recipient: recipient, + subject: subject, + pdfName: pdfName, + html: body, + mailProvider: obj.mailProvider, + }; + const res = await axios.post(serverUrl + '/functions/sendmailv3', params, { + headers: { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': APPID, + 'X-Parse-Master-Key': masterKEY, + }, + }); + // console.log('Res ', res); +} + +// `sendDoctoWebhook` is used to send res data of document on webhook +async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) { + let signers = []; + if (signUser) { + signers = { + name: signUser?.Name, + email: signUser?.Email, + phone: signUser?.Phone, + }; + } else { + signers = doc?.Signers?.map(x => ({ + name: x.Name, + email: x.Email, + phone: x.Phone, + })) || [ + { + name: doc?.ExtUserPtr?.Name, + email: doc?.ExtUserPtr?.Email, + phone: doc?.ExtUserPtr?.Phone, + }, + ]; + } + + if (doc.ExtUserPtr?.Webhook) { + const time = + event === 'signed' + ? { signer: signers, signedAt: new Date() } + : { signers: signers, completedAt: new Date() }; + const certificate = certificateUrl ? { certificate: certificateUrl } : {}; + const params = { + event: event, + objectId: doc?.objectId, + file: Url || '', + ...certificate, + name: doc?.Name, + note: doc?.Note || '', + description: doc?.Description || '', + ...time, + createdAt: doc?.createdAt, + }; + axios + .post(doc?.ExtUserPtr?.Webhook, params, { + headers: { 'Content-Type': 'application/json' }, + }) + .then(res => { + try { + // console.log('res ', res); + const webhook = new Parse.Object('contracts_Webhook'); + webhook.set('Log', res?.status); + webhook.set('UserId', { + __type: 'Pointer', + className: '_User', + objectId: doc.ExtUserPtr.UserId.objectId, + }); + webhook.save(null, { useMasterKey: true }); + } catch (err) { + console.log('err save in contracts_Webhook', err.message); + } + }) + .catch(err => { + console.log('Err send data to webhook', err.message); + try { + const webhook = new Parse.Object('contracts_Webhook'); + webhook.set('Log', err?.status); + webhook.set('UserId', { + __type: 'Pointer', + className: '_User', + objectId: doc.ExtUserPtr.UserId.objectId, + }); + webhook.save(null, { useMasterKey: true }); + } catch (err) { + console.log('err save in contracts_Webhook', err.message); + } + }); + // console.log('res ', res.data); + } +} + +// `sendMailsaveCertifcate` is used send completion mail and update complete status of document +const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailProvider, userId) => { + const certificate = await GenerateCertificate(doc); + const certificatePdf = await PDFDocument.load(certificate); + const p12 = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null }); + // `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate + pdflibAddPlaceholder({ + pdfDoc: certificatePdf, + reason: 'Digitally signed by OpenSign.', + location: 'n/a', + name: eSignName, + contactInfo: eSigncontact, + signatureLength: 15000, + }); + const pdfWithPlaceholderBytes = await certificatePdf.save(); + const CertificateBuffer = Buffer.from(pdfWithPlaceholderBytes); + //`new signPDF` create new instance of CertificateBuffer and p12Buffer + const certificateOBJ = new SignPdf(); + // `signedCertificate` is used to sign certificate digitally + const signedCertificate = await certificateOBJ.sign(CertificateBuffer, p12); + + //below is used to save signed certificate in exports folder + fs.writeFileSync('./exports/certificate.pdf', signedCertificate); + const file = await uploadFile('certificate.pdf', './exports/certificate.pdf'); + const body = { CertificateUrl: file.imageUrl }; + await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, { + headers: { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': APPID, + 'X-Parse-Master-Key': masterKEY, + }, + }); + // used in API only + if (doc.IsSendMail && doc.IsSendMail === false) { + console.log("don't send mail"); + } else { + const mailObj = { + url: url, + isCustomMail: isCustomMail, + doc: doc, + mailProvider: mailProvider, + }; + sendCompletedMail(mailObj); + } + + saveFileUsage(CertificateBuffer.length, file.imageUrl, userId); + sendDoctoWebhook(doc, url, 'completed', '', file.imageUrl); +}; +/** + * + * @param docId Id of Document in which user is signing + * @param pdfFile base64 of pdfFile which you want sign + * @returns if success {status, data} else {status, message} + */ +async function PDF(req) { + try { + if (!req?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } else { + const password = ''; + const authUser = req?.user?.toJSON(); + const docId = req.params.docId; + const reqUserId = req.params.userId; + const isCustomMail = req.params.isCustomCompletionMail || false; + const mailProvider = req.params.mailProvider || ''; + const sign = req.params.signature || ''; + // below bode is used to get info of docId + const docQuery = new Parse.Query('contracts_Document'); + docQuery.include('ExtUserPtr,Signers'); + docQuery.equalTo('objectId', docId); + const resDoc = await docQuery.first({ useMasterKey: true }); + if (!resDoc) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.'); + } + const _resDoc = resDoc?.toJSON(); + + let signUser; + let className; + // `reqUserId` is send throught pdfrequest signing flow + if (reqUserId) { + // to get contracts_Contactbook details for currentuser from reqUserId + const _contractUser = _resDoc.Signers.find(x => x.objectId === reqUserId); + if (_contractUser) { + signUser = _contractUser; + className = 'contracts_Contactbook'; + } + } else { + className = 'contracts_Users'; + signUser = _resDoc.ExtUserPtr; + } + + const username = signUser.Name; + const userEmail = signUser.Email; + if (req.params.pdfFile) { + // `PdfBuffer` used to create buffer from pdf file + let PdfBuffer = Buffer.from(req.params.pdfFile, 'base64'); + // `P12Buffer` used to create buffer from p12 certificate + const pfxFile = process.env.PFX_BASE64; + // const P12Buffer = fs.readFileSync(); + const P12Buffer = Buffer.from(pfxFile, 'base64'); + const p12Cert = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null }); + + const UserPtr = { + __type: 'Pointer', + className: className, + objectId: signUser.objectId, + }; + const obj = { + UserPtr: UserPtr, + SignedUrl: '', + Activity: 'Signed', + ipAddress: req.headers['x-real-ip'], + }; + let updateAuditTrail; + if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) { + updateAuditTrail = [..._resDoc.AuditTrail, obj]; + } else { + updateAuditTrail = [obj]; + } + + const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed'); + let isCompleted = false; + if (_resDoc.Signers && _resDoc.Signers.length > 0) { + if (auditTrail.length === _resDoc.Signers.length) { + isCompleted = true; + } + } else { + isCompleted = true; + } + const randomNumber = Math.floor(Math.random() * 5000); + const name = `exported_file_${randomNumber}.pdf`; + const pdfName = `./exports/${name}`; + let pdfSize = PdfBuffer.length; + if (isCompleted) { + const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>'); + if (signersName && signersName.length > 0) { + // `pdflibAddPlaceholder` is used to add code of only digitial sign without widget + const pdfDoc = await PDFDocument.load(PdfBuffer); + pdflibAddPlaceholder({ + pdfDoc: pdfDoc, + reason: 'Digitally signed by OpenSign for ' + signersName?.join(', '), + location: 'n/a', + name: eSignName, + contactInfo: eSigncontact, + signatureLength: 15000, + }); + const pdfWithPlaceholderBytes = await pdfDoc.save(); + PdfBuffer = Buffer.from(pdfWithPlaceholderBytes); + } else { + // `pdflibAddPlaceholder` is used to add code of only digitial sign without widget (signyourself) + const pdfDoc = await PDFDocument.load(PdfBuffer); + pdflibAddPlaceholder({ + pdfDoc: pdfDoc, + reason: 'Digitally signed by OpenSign for ' + username + ' <' + userEmail + '>', + location: 'n/a', + name: eSignName, + contactInfo: eSigncontact, + signatureLength: 15000, + }); + const pdfWithPlaceholderBytes = await pdfDoc.save(); + PdfBuffer = Buffer.from(pdfWithPlaceholderBytes); + } + //`new signPDF` create new instance of pdfBuffer and p12Buffer + const OBJ = new SignPdf(); + // `signedDocs` is used to signpdf digitally + const signedDocs = await OBJ.sign(PdfBuffer, p12Cert); + + //`saveUrl` is used to save signed pdf in exports folder + const saveUrl = fs.writeFileSync(pdfName, signedDocs); + pdfSize = signedDocs.length; + } else { + //`saveUrl` is used to save signed pdf in exports folder + const saveUrl = fs.writeFileSync(pdfName, PdfBuffer); + pdfSize = PdfBuffer.length; + } + + // `uploadFile` is used to upload pdf to aws s3 and get it's url + const data = await uploadFile(name, pdfName); + + if (data && data.imageUrl) { + // `axios` is used to update signed pdf url in contracts_Document classes for given DocId + const updatedDoc = await updateDoc( + req.params.docId, //docId + data.imageUrl, // url + signUser.objectId, // userID + req.headers['x-real-ip'], // client ipAddress, + _resDoc, // auditTrail, signers, etc data + className, + sign + ); + sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser); + saveFileUsage(pdfSize, data.imageUrl, authUser.objectId); + if (updatedDoc && updatedDoc.isCompleted) { + const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail }; + sendMailsaveCertifcate( + doc, + P12Buffer, + data.imageUrl, + isCustomMail, + mailProvider, + authUser.objectId + ); + } + // `fs.unlinkSync` is used to remove exported signed pdf file from exports folder + fs.unlinkSync(pdfName); + console.log(`New Signed PDF created called: ${pdfName}`); + if (updatedDoc.message === 'success') { + return { status: 'success', data: data.imageUrl }; + } else { + const error = new Error('Please provide required parameters!'); + error.code = 400; // Set the error code (e.g., 400 for bad request) + throw error; + } + } + } else { + const error = new Error('Pdf file not present!'); + error.code = 400; // Set the error code (e.g., 400 for bad request) + throw error; + } + } + } catch (err) { + console.log('Err in signpdf', err); + throw err; + } +} +export default PDF; diff --git a/apps/OpenSignServer/cloud/parsefunction/pdf/PDF.min.js b/apps/OpenSignServer/cloud/parsefunction/pdf/PDF.min.js deleted file mode 100644 index d29c624c9..000000000 --- a/apps/OpenSignServer/cloud/parsefunction/pdf/PDF.min.js +++ /dev/null @@ -1,337 +0,0 @@ -import fs from 'node:fs'; -import axios from 'axios'; -import { SignPdf } from '@signpdf/signpdf'; -import { P12Signer } from '@signpdf/signer-p12'; -import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib'; -import { PDFDocument } from 'pdf-lib'; -import { replaceMailVaribles, saveFileUsage } from '../../../Utils.js'; -import GenerateCertificate from './GenerateCertificate.js'; -const serverUrl = process.env.SERVER_URL, - APPID = process.env.APP_ID, - masterKEY = process.env.MASTER_KEY, - eSignName = 'opensign', - eSigncontact = 'hello@opensignlabs.com'; -async function uploadFile(e, t) { - try { - var a = fs.readFileSync(t), - r = new Parse.File(e, [...a], 'application/pdf'), - i = (await r.save({ useMasterKey: !0 }), r.url()); - return { imageUrl: i }; - } catch (e) { - console.log('Err ', e), fs.unlinkSync(t); - } -} -async function updateDoc(a, r, i, n, s, o, l) { - try { - var c, - d, - p = { - UserPtr: { __type: 'Pointer', className: o, objectId: i }, - SignedUrl: r, - Activity: 'Signed', - ipAddress: n, - SignedOn: new Date(), - Signature: l, - }; - let e; - var m = (e = - s.AuditTrail && 0 < s.AuditTrail.length - ? (-1 !== - (d = (c = JSON.parse(JSON.stringify(s.AuditTrail))).findIndex( - e => e.UserPtr.objectId === i && 'Created' !== e.Activity - )) - ? (c[d] = { ...c[d], ...p }) - : c.push(p), - c) - : [p]).filter(e => 'Signed' === e.Activity); - let t = !1; - !((s.Signers && 0 < s.Signers.length && m.length !== s.Signers.length) || !(t = !0)); - var g = { SignedUrl: r, AuditTrail: e, IsCompleted: t }; - await axios.put(serverUrl + '/classes/contracts_Document/' + a, g, { - headers: { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': APPID, - 'X-Parse-Master-Key': masterKEY, - }, - }); - return { isCompleted: t, message: 'success', AuditTrail: e }; - } catch (e) { - return console.log('update doc err ', e), 'err'; - } -} -async function sendCompletedMail(e) { - var t = e.url, - a = e.doc, - r = e.doc.ExtUserPtr, - i = a.Name, - n = r.Email; - let s = `Document "${i}" has been signed by all parties`, - o = - "

Document signed successfully

All parties have successfully signed the document " + - `"${i}"` + - '. Kindly download the document from the attachment.

This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' + - r.Email + - ' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ here.

'; - if (e?.isCustomMail) - try { - var l, - c, - d, - p, - m, - g = new Parse.Query('partners_Tenant'); - g.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: r.UserId.objectId }); - const f = await g.first(); - f && - ((l = JSON.parse(JSON.stringify(f)))?.CompletionSubject && (s = l?.CompletionSubject), - l?.CompletionBody && (o = l?.CompletionBody), - (c = a.ExpiryDate.iso), - (d = new Date(c).toLocaleDateString('en-US', { - day: 'numeric', - month: 'long', - year: 'numeric', - })), - (p = { - document_title: i, - sender_name: r.Name, - sender_mail: r.Email, - sender_phone: r.Phone, - receiver_name: r.Name, - receiver_email: r.Email, - receiver_phone: r.Phone, - expiry_date: d, - company_name: r.Company, - }), - (m = replaceMailVaribles(s, o, p)), - (s = m.subject), - (o = m.body)); - } catch (e) { - console.log('error in fetch tenant in signpdf', e.message); - } - g = { - extUserId: r.objectId, - url: t, - from: 'OpenSign™', - recipient: n, - subject: s, - pdfName: i, - html: o, - mailProvider: e.mailProvider, - }; - await axios.post(serverUrl + '/functions/sendmailv3', g, { - headers: { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': APPID, - 'X-Parse-Master-Key': masterKEY, - }, - }); -} -async function sendDoctoWebhook(a, e, t, r, i) { - let n = []; - (n = r - ? { name: r?.Name, email: r?.Email, phone: r?.Phone } - : a?.Signers?.map(e => ({ name: e.Name, email: e.Email, phone: e.Phone })) || [ - { name: a?.ExtUserPtr?.Name, email: a?.ExtUserPtr?.Email, phone: a?.ExtUserPtr?.Phone }, - ]), - a.ExtUserPtr?.Webhook && - ((r = - 'signed' === t - ? { signer: n, signedAt: new Date() } - : { signers: n, completedAt: new Date() }), - (t = { - event: t, - objectId: a?.objectId, - file: e || '', - ...(i ? { certificate: i } : {}), - name: a?.Name, - note: a?.Note || '', - description: a?.Description || '', - ...r, - createdAt: a?.createdAt, - }), - axios - .post(a?.ExtUserPtr?.Webhook, t, { headers: { 'Content-Type': 'application/json' } }) - .then(e => { - try { - var t = new Parse.Object('contracts_Webhook'); - t.set('Log', e?.status), - t.set('UserId', { - __type: 'Pointer', - className: '_User', - objectId: a.ExtUserPtr.UserId.objectId, - }), - t.save(null, { useMasterKey: !0 }); - } catch (e) { - console.log('err save in contracts_Webhook', e.message); - } - }) - .catch(e => { - console.log('Err send data to webhook', e.message); - try { - var t = new Parse.Object('contracts_Webhook'); - t.set('Log', e?.status), - t.set('UserId', { - __type: 'Pointer', - className: '_User', - objectId: a.ExtUserPtr.UserId.objectId, - }), - t.save(null, { useMasterKey: !0 }); - } catch (e) { - console.log('err save in contracts_Webhook', e.message); - } - })); -} -const sendMailsaveCertifcate = async (e, t, a, r, i, n) => { - var s = await GenerateCertificate(e), - s = await PDFDocument.load(s), - t = new P12Signer(t, { passphrase: process.env.PASS_PHRASE || null }), - s = - (pdflibAddPlaceholder({ - pdfDoc: s, - reason: 'Digitally signed by OpenSign.', - location: 'n/a', - name: eSignName, - contactInfo: eSigncontact, - signatureLength: 15e3, - }), - await s.save()), - s = Buffer.from(s), - t = await new SignPdf().sign(s, t), - t = - (fs.writeFileSync('./exports/certificate.pdf', t), - await uploadFile('certificate.pdf', './exports/certificate.pdf')), - o = { CertificateUrl: t.imageUrl }; - await axios.put(serverUrl + '/classes/contracts_Document/' + e.objectId, o, { - headers: { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': APPID, - 'X-Parse-Master-Key': masterKEY, - }, - }), - e.IsSendMail && !1 === e.IsSendMail - ? console.log("don't send mail") - : sendCompletedMail({ url: a, isCustomMail: r, doc: e, mailProvider: i }), - saveFileUsage(s.length, t.imageUrl, n), - sendDoctoWebhook(e, a, 'completed', '', t.imageUrl); -}; -async function PDF(s) { - try { - if (!s?.user) - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); - { - var o = s?.user?.toJSON(), - e = s.params.docId; - const C = s.params.userId; - var l = s.params.isCustomCompletionMail || !1, - c = s.params.mailProvider || '', - d = s.params.signature || '', - t = new Parse.Query('contracts_Document'), - a = - (t.include('ExtUserPtr,Signers'), - t.equalTo('objectId', e), - await t.first({ useMasterKey: !0 })); - if (!a) throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.'); - var r, - p = a?.toJSON(); - let i, n; - C - ? ((r = p.Signers.find(e => e.objectId === C)), - console.log('_contractUser ', r), - r && ((i = r), (n = 'contracts_Contactbook'))) - : ((n = 'contracts_Users'), (i = p.ExtUserPtr)); - var m, - g = i.Name, - f = i.Email; - if (!s.params.pdfFile) throw (((m = new Error('Pdf file not present!')).code = 400), m); - { - let e = Buffer.from(s.params.pdfFile, 'base64'); - var u = process.env.PFX_BASE64, - h = Buffer.from(u, 'base64'), - y = new P12Signer(h, { passphrase: process.env.PASS_PHRASE || null }), - P = { - UserPtr: { __type: 'Pointer', className: n, objectId: i.objectId }, - SignedUrl: '', - Activity: 'Signed', - ipAddress: s.headers['x-real-ip'], - }; - let t; - var v = (t = p.AuditTrail && 0 < p.AuditTrail.length ? [...p.AuditTrail, P] : [P]).filter( - e => 'Signed' === e.Activity - ); - let a = !1; - !((p.Signers && 0 < p.Signers.length && v.length !== p.Signers.length) || !(a = !0)); - var S, - b, - w, - U, - I, - _, - E = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`, - D = './exports/' + E; - let r = e.length; - r = ( - a - ? ((S = p.Signers?.map(e => e.Name + ' <' + e.Email + '>')), - (e = - S && 0 < S.length - ? ((b = await PDFDocument.load(e)), - pdflibAddPlaceholder({ - pdfDoc: b, - reason: 'Digitally signed by OpenSign for ' + S?.join(', '), - location: 'n/a', - name: eSignName, - contactInfo: eSigncontact, - signatureLength: 15e3, - }), - (w = await b.save()), - Buffer.from(w)) - : ((U = await PDFDocument.load(e)), - pdflibAddPlaceholder({ - pdfDoc: U, - reason: 'Digitally signed by OpenSign for ' + g + ' <' + f + '>', - location: 'n/a', - name: eSignName, - contactInfo: eSigncontact, - signatureLength: 15e3, - }), - (I = await U.save()), - Buffer.from(I))), - (_ = await new SignPdf().sign(e, y)), - fs.writeFileSync(D, _), - _) - : (fs.writeFileSync(D, e), e) - ).length; - var A = await uploadFile(E, D); - if (A && A.imageUrl) { - var x, - j, - N = await updateDoc( - s.params.docId, - A.imageUrl, - i.objectId, - s.headers['x-real-ip'], - p, - n, - d - ); - if ( - (sendDoctoWebhook(p, A.imageUrl, 'signed', i), - saveFileUsage(r, A.imageUrl, o.objectId), - N && - N.isCompleted && - ((x = { ...p, AuditTrail: N.AuditTrail }), - sendMailsaveCertifcate(x, h, A.imageUrl, l, c, o.objectId)), - fs.unlinkSync(D), - console.log('New Signed PDF created called: ' + D), - 'success' === N.message) - ) - return { status: 'success', data: A.imageUrl }; - throw (((j = new Error('Please provide required parameters!')).code = 400), j); - } - } - } - } catch (e) { - throw (console.log('Err in signpdf', e), e); - } -} -export default PDF;