Search code examples
reactjsnode.jsexpressdocusignapidocusign-sdk

Docusign focus view within React app, loading icon goes into infinite loop with CSP errors in console log


I have been trying to integrate docusign with focus view within my react app. I am using docusign SDK for nodejs and express server makes all the API requests and fetches the data through docusign so I am able to fetch the URL using postman and visiting the link directly opens up the form fine, but when embed fresh URL in react app, the docusign loading icon goes in infinite loop in focus view.

The console log show errors with Content Security Policy. Haven't had much luck with docusign react sample app.

Below is the code taken from docusign How-to guide and adjusted for react: LINK

import React, { useEffect } from 'react';
import PropTypes from 'prop-types';

function DocumentSignUI({ integrationKey, docUrl }) {
  useEffect(() => {
    const loadDocuSign = async () => {
      try {
        const docusign = await window.DocuSign.loadDocuSign(integrationKey);
        const url = `${docUrl}&output=embed`;

        const signing = docusign.signing({
          url,
          displayFormat: 'focused',
        });

        signing.mount('#agreement');
      } catch (ex) {
        console.error(ex);
        // Handle error
      }
    };

    console.log('DOCUSIGN RENDER');
    loadDocuSign();
  }, []);

  return (
    <div className="docusign-agreement" id="agreement" />
  );
}

DocumentSignUI.propTypes = {
  integrationKey: PropTypes.string.isRequired,
  docUrl: PropTypes.string.isRequired,
};

export default DocumentSignUI;

Errors encountered in console log (Chromium) Chromium Console Errors

Errors encountered in console log (Firefox) Firefox Console Errors

Tried disabling content security policy on server it didnt work, tried adding meta tags in index.html and that didn't work either.

Here is the code of API calls

router.get("/sign-document", processFile, async (req, res) => {
  try {
    var userData = "";

    await checkToken(req);


    var apiClient = new docusign.ApiClient({
      basePath: basePath,
      oAuthBasePath: oAuthBasePath + "/oauth/token",
    });

    apiClient.addDefaultHeader(
      "Authorization",
      "Basic KEY"
    );

    userData = await apiClient.getUserInfo(req.session.access_token);

    var baseUri = userData.accounts[0].baseUri;
    var accountDomain = baseUri.split("/v2");
    apiClient.setBasePath(accountDomain[0] + "/restapi");

    const envelopeArgs = {
      signerEmail: "EMAIL",
      signerName: "NAME",
      signerClientId: DOCUSIGN_JWT_CLIENT_ID,
      docFile: req.file,
      status: "created",
    };

    const envelope = makeEnvelope(envelopeArgs);


    const envelopesApi = await getEnvelopes(req);



    const userEnvelopeResult = await envelopesApi.createEnvelope(
      userData.accounts[0].accountId,
      { envelopeDefinition: envelope }
    );

    const recepientRequest = {
      dsReturnUrl: "https://localhost:5173/success",
      signerEmail: "EMAIL",
      signerName: "NAME",
      signerClientId: DOCUSIGN_JWT_CLIENT_ID,
    };


    const viewRequest = await makeRecipientViewRequest(recepientRequest);


    const viewRequestResult = await envelopesApi.createRecipientView(
      userData.accounts[0].accountId,
      userEnvelopeResult.envelopeId,
      {
        recipientViewRequest: viewRequest,
        frameAncestors: [
          "http://localhost",
          "https://apps-d.docusign.com",
        ],
        messageOrigins: ["https://apps-d.docusign.com"],
        signerEmail: "EMAIL",
        signerName: "NAME"
      }
    );


    res.status(200).json({
      envelopeId: userEnvelopeResult.envelopeId,
      redirectUrl: viewRequestResult.url,
    });
  } catch (error) {
    console.error("Error processing request:", error);
    res.status(500).json({
      success: false,
      message: "An error occurred while processing the request",
    });
  }
});

Here are the functions

    const checkToken = async (req) => {
  try {
    var apiClient = new docusign.ApiClient({
      basePath: basePath,
      oAuthBasePath: oAuthBasePath + "/oauth/token",
    });

    apiClient.addDefaultHeader(
      "Authorization",
      "Basic OTAyY2IxNmYtYjBmZi00MzY2LWIyYzQtZDBmNWM3MTk1NTc0OjhlMzJjNzI0LTI1NjMtNDM0MC1iMTk5LWQxYTEwMTBhODM5YQ=="
    );

    var oauthLoginUrl = await apiClient.getJWTUri(
      DOCUSIGN_JWT_CLIENT_ID,
      "http://localhost:5173/",
      oAuthBasePath
    );
    var scopes = [oAuth.Scope.IMPERSONATION, oAuth.Scope.SIGNATURE];

    if (req.session.access_token && Date.now() < req.session.expires_at) {
      console.log("re-using access token ", req.session.access_token);
    } else {
      console.log("creating new access token");
      jWTResponse = await apiClient.requestJWTUserToken(
        DOCUSIGN_JWT_CLIENT_ID,
        DOCUSIGN_IMPERSONATED_USER_GUID,
        scopes,
        DOCUSIGN_RSA_KEY,
        36000
      );

      console.log(jWTResponse.body.access_token);

      req.session.access_token = jWTResponse.body.access_token;

      req.session.expires_at =
        Date.now() + (jWTResponse.body.expires_in - 60) * 1000;
    }
  } catch (error) {
    console.error("Error while checking token:", error);
  }
};

const getEnvelopes = async (req) => {
  let dsApiClient = new docusign.ApiClient();
  dsApiClient.setBasePath(basePath);
  dsApiClient.addDefaultHeader(
    "Authorization",
    "Bearer " + req.session.access_token
  );
  return new docusign.EnvelopesApi(dsApiClient);
};

const makeEnvelope = (args) => {
  let docPdfBytes;
  // const filePath = path.resolve(__dirname, args.docFile);
  // docPdfBytes = fs.readFileSync(filePath);

  console.log(args.docFile);

  let env = new docusign.EnvelopeDefinition();
  env.emailSubject = "Please sign this document";

  let doc1 = new docusign.Document();
  let doc1b64 = Buffer.from(args.docFile.buffer).toString("base64");
  doc1.documentBase64 = doc1b64;
  doc1.name = "Lorem Ipsum";
  doc1.fileExtension = "pdf";
  doc1.documentId = "3";

  env.documents = [doc1];

  let signer1 = docusign.Signer.constructFromObject({
    email: args.signerEmail,
    name: args.signerName,
    clientUserId: args.signerClientId,
    recipientId: 1,
  });

  let signHere1 = docusign.SignHere.constructFromObject({
    anchorString: "/sn1/",
    anchorYOffset: "10",
    anchorUnits: "pixels",
    anchorXOffset: "20",
  });
  let signer1Tabs = docusign.Tabs.constructFromObject({
    signHereTabs: [signHere1],
  });
  signer1.tabs = signer1Tabs;

  let recipients = docusign.Recipients.constructFromObject({
    signers: [signer1],
  });
  env.recipients = recipients;
  env.status = "sent";

  return env;
};

const makeRecipientViewRequest = (args) => {
  let viewRequest = new docusign.RecipientViewRequest();
  viewRequest.returnUrl = args.dsReturnUrl;

  viewRequest.authenticationMethod = "none";

  viewRequest.email = args.signerEmail;
  viewRequest.userName = args.signerName;
  viewRequest.clientUserId = args.signerClientId;

  viewRequest.frameAncestors = [
    "http://localhost:4000/api",
    "https://apps-d.docusign.com",
  ];
  viewRequest.messageOrigins = ["https://apps-d.docusign.com"];

  return viewRequest;
};

Solution

  • The issue was (confirmed already in comments) that

    frameAncestors (An array that represents the originating domain for the frame in which focused view will be rendered. It must include your site’s URL along with https://apps-d.docusign.comhttps://apps-d.docusign.com, opens in new window for developer environments or https://apps.docusign.comhttps://apps.docusign.com, opens in new window for production environments. Your site must have a valid SSL certificate unless you are using a http://localhost domain.)

    Was not set correctly.

    This is done in server code (or REST API code can still be from client using CORS) and is the issue here. While you will still get back a URL you can use, it will not load if you didn't set frameAncestors correctly.

    Here is a snippet:

    viewRequest.frameAncestors = ['http://localhost:3000', 'https://apps-d.docusign.com'];
    viewRequest.messageOrigins = ['https://apps-d.docusign.com'];