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;