Search code examples
reactjsdocusignapi

How to embed DocuSign console UI into React app


I am trying to embedd DocuSign envelope sending console into my react application. Is there any API to integrate this. I am unable to find solution from past 2 days. If anyone implemented this please guide me. Thanks in advance

I have tried below blog

https://developers.docusign.com/docs/esign-rest-api/how-to/embed-ui/


Solution

  • I assume you don't have a server component and want to make the API calls from your React app from the client-side (from the browser).

    You will need to configure CORS for your application. This is required to make API calls from the browser.

    You can see a working example on CodePen

    Here is the codePen code:

    // Copyright © 2022 DocuSign, Inc.
    // License: MIT Open Source https://opensource.org/licenses/MIT
    
    import {
        CallApi,
        ImplicitGrant,
        UserInfo
        // View the source at https://codepen.io/docusign/pen/LYBKqxb
    } from "https://codepen.io/docusign/pen/LYBKqxb.js";
    import {
        msg,
        htmlMsg,
        adjustRows,
        errMsg,
        workingUpdate,
        usingHttps
        // View the source at https://codepen.io/docusign/pen/QWBXYzX
    } from "https://codepen.io/docusign/pen/QWBXYzX.js";
    
    $(function () {
        // Set basic variables
        const dsReturnUrl = "https://docusign.github.io/jsfiddleDsResponse.html";
        const logLevel = 0; // 0 is terse; 9 is verbose
        let signingCeremonyWindow = null;
    
        // Example settings
        const docUrl = "https://docusign.github.io/examples/docs/anchorfields.pdf";
        const docViewUrl =
            "https://docusign.github.io/examples/docs/anchorfields_view.pdf";
    
        debugger; // uncomment with debugger open to find the right JS file.
    
        /*
         * The doit function is the example that is triggered by the
         * button. The user is already logged in (we have an access token).
         */
        let doit = async function doitf(event) {
            $("#doit").addClass("hide");
            if (!checkToken()) {
                // Check that we have a valid token
                return;
            }
            workingUpdate(true);
            const signer = {
                name: data.userInfo.name,
                email: data.userInfo.email,
                clientUserId: 1000
            };
            const envelopeId = await createEnvelope(signer);
            if (envelopeId) {
                msg(`Envelope ${envelopeId} created.`);
                await embeddedSigningCeremony({
                    envelopeId: envelopeId,
                    signer: signer
                });
            }
            $("#doit").removeClass("hide");
            workingUpdate(false);
        };
        doit = doit.bind(this);
    
        /*
         *  Create the envelope by completely specifying the envelope.
         *  Often the recommended alternative is to first create a template
         *  on the DocuSign platform, then reference the template when
         *  creating the envelope. See the other examples...
         */
        async function createEnvelope({ name, email, clientUserId }) {
            const docB64 = await data.callApi.getDocB64(docUrl); // fetch a document
    
            const req = {
                emailSubject: "Please sign the attached document",
                status: "sent",
                customFields: {
                    listCustomFields: [
                        {
                            name: "Envelope list custom field 1",
                            show: "true",
                            value: "value 1"
                        },
                        {
                            name: "Envelope list custom field 2",
                            show: "true",
                            value: "value 2"
                        }
                    ],
                    textCustomFields: [
                        {
                            name: "Envelope text custom field 1",
                            show: "true",
                            value: "value 1"
                        },
                        {
                            name: "Envelope text custom field 2",
                            show: "true",
                            value: "value 2"
                        }
                    ]
                },
                documents: [
                    {
                        name: "Example document",
                        documentBase64: docB64,
                        fileExtension: "pdf",
                        documentId: "1",
                        documentFields: [
                            {
                                name: "doc field 1",
                                value: "val 1"
                            },
                            {
                                name: "doc field 2",
                                value: "val 2"
                            }
                        ]
                    }
                ],
                recipients: {
                    signers: [
                        {
                            email: email,
                            name: name,
                            clientUserId: clientUserId,
                            customFields: ["field1: value 1", "field2: value 2"],
                            recipientId: "1",
                            tabs: {
                                signHereTabs: [
                                    {
                                        anchorString: "/sig1/",
                                        anchorXOffset: "20",
                                        anchorUnits: "pixels"
                                    }
                                ],
                                checkboxTabs: [
                                    {
                                        anchorString: "/sig1/",
                                        anchorXOffset: "180",
                                        anchorUnits: "pixels",
                                        tabLabel: "checkbox 1",
                                        tabGroupLabels: ["checkbox group"]
                                    },
                                    {
                                        anchorString: "/sig1/",
                                        anchorXOffset: "180",
                                        anchorYOffset: "20",
                                        anchorUnits: "pixels",
                                        tabLabel: "checkbox 2",
                                        tabGroupLabels: ["checkbox group"]
                                    }
                                ],
                                tabGroups: [
                                    {
                                        groupLabel: "checkbox group",
                                        groupRule: "SelectExactly",
                                        minimumRequired: "1",
                                        maximumAllowed: "1",
                                        validationMessage:
                                            "Please check one option",
                                        tabScope: "document",
                                        pageNumber: "1",
                                        documentId: "1"
                                    }
                                ],
                                textTabs: [
                                    {
                                        anchorString: "/sig1/",
                                        anchorXOffset: "195",
                                        anchorUnits: "pixels",
                                        value: "I agree with license 1",
                                        locked: "true",
                                        font: "Helvetica",
                                        fontSize: "Size12",
                                        bold: "true"
                                    },
                                    {
                                        anchorString: "/sig1/",
                                        anchorXOffset: "195",
                                        anchorYOffset: "20",
                                        anchorUnits: "pixels",
                                        value: "I agree with license 2",
                                        locked: "true",
                                        font: "Helvetica",
                                        fontSize: "Size12",
                                        bold: "true"
                                    },
                                    {
                                        anchorString: "/sig1/",
                                        anchorXOffset: "20",
                                        anchorYOffset: "50",
                                        anchorUnits: "pixels",
                                        bold: "true",
                                        font: "Helvetica",
                                        fontSize: "Size14",
                                        fontColor: "NavyBlue",
                                        value:
                                            "Addendum\n‣ Item 1\n‣ Item 2\nThis is a multi-line read-only text field"
                                    }
                                ]
                            }
                        }
                    ]
                }
            };
    
            // Make the create envelope API call
            msg(`Creating envelope.`);
            const apiMethod = `/accounts/${data.userInfo.defaultAccount}/envelopes`;
            const httpMethod = "POST";
            const results = await data.callApi.callApiJson({
                apiMethod: apiMethod,
                httpMethod: httpMethod,
                req: req
            });
            if (results === false) {
                errMsg(data.callApi.errMsg);
                return false;
            }
            if (logLevel > 0) {
                htmlMsg(
                    `<p>Envelope created. Response:</p><p><pre><code>${JSON.stringify(
                        results,
                        null,
                        4
                    )}</code></pre></p>`
                );
            }
            return results.envelopeId;
        }
    
        /*
         * Create an embedded signing ceremony, open a new tab with it
         */
        async function embeddedSigningCeremony({ envelopeId, signer }) {
            const req = {
                returnUrl: dsReturnUrl,
                authenticationMethod: "None",
                clientUserId: signer.clientUserId,
                email: signer.email,
                userName: signer.name
            };
    
            // Make the API call
            const apiMethod = `/accounts/${data.userInfo.defaultAccount}/envelopes/${envelopeId}/views/recipient`;
            const httpMethod = "POST";
            const results = await data.callApi.callApiJson({
                apiMethod: apiMethod,
                httpMethod: httpMethod,
                req: req
            });
            if (results === false) {
                errMsg(data.callApi.errMsg);
                return false;
            }
            if (logLevel > 5) {
                htmlMsg(
                    `<p>Embedded Signing Ceremony created. Response:</p><p><pre><code>${JSON.stringify(
                        results,
                        null,
                        4
                    )}</code></pre></p>`
                );
            }
            msg(`Displaying signing ceremony...`);
            signingCeremonyWindow = window.open(results.url, "_blank");
            if(!signingCeremonyWindow || signingCeremonyWindow.closed || 
               typeof signingCeremonyWindow.closed=='undefined') {
                // popup blocked
                alert ("Please enable the popup window signing ceremony");
            }
            signingCeremonyWindow.focus();
            return true;
        }
    
        /*
         * signingCeremonyEnded
         * After the signing ceremony ends (in its own tab),
         * the browser tab is redirected to the returnUrl, defined
         * in constant dsReturnUrl.
         * Source:
         * https://github.com/docusign/docusign.github.io/blob/master/jsfiddleDsResponse.html
         *
         * That page runs Javascript to send a message via window.postMessage.
         * The message is tagged with source `dsResponse`.
         * The function messageListener (in mainline section) receives and
         * dispatches the different types of incoming messages.
         * When a dsResponse message is received, this function is called.
         */
        function signingCeremonyEnded(data) {
            if (data.source !== "dsResponse") {
                return; // Sanity check
            }
            signingCeremonyWindow.close(); // close the browser tab
            const href = data.href; // "http://localhost:3000/?event=signing_complete"
            const sections = href.split("?");
            const hasEvents = sections.length === 2;
            const qps = hasEvents ? sections[1].split("&") : [];
            if (!hasEvents) {
                errMsg(`Unexpected signing ceremony response: ${data.href}.`);
                return;
            }
            let msg = `<p><b>Signing Ceremony Response</b><br/>`;
            msg += `<small>Information Security tip: do not make business decisions based on this data since they can be spoofed. Instead, use the API.</small>`;
    
            qps.forEach((i) => {
                const parts = i.split("=");
                msg += `<br/>Query parameter <b>${parts[0]} = "${parts[1]}"</b>`;
            });
            msg += "</p>";
            htmlMsg(msg);
        }
        
        /////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////
    
        /* Checks that the access token is still good.
         * Prompts for login if not
         */
        function checkToken() {
            if (data.implicitGrant.checkToken()) {
                // ok
                return true;
            } else {
                // not ok
                $("#login").removeClass("hide");
                $("#doit").addClass("hide");
                // reset everything
                msg("Please login");
            }
        }
        
        /*
         * Receives and dispatches incoming messages
         */
        let messageListener = async function messageListenerf(event) {
            if (!event.data) {
                return;
            }
            const source = event.data.source;
            if (source === "dsResponse") {
                signingCeremonyEnded(event.data);
                return;
            }
            if (source === "oauthResponse" && data.implicitGrant) {
                await implicitGrantMsg(event.data);
                return;
            }
        };
        messageListener = messageListener.bind(this);
    
        /*
         * Process incoming implicit grant response
         */
        async function implicitGrantMsg(eventData) {
            const oAuthResponse = data.implicitGrant.handleMessage(eventData);
            if (oAuthResponse === "ok") {
                if (await completeLogin()) {
                    data.loggedIn();
                }
            } else if (oAuthResponse === "error") {
                $("#login").removeClass("hide");
                const errHtml = `<p class="text-danger">${data.implicitGrant.errMsg}</p>`;
                htmlMsg(errHtml);
            }
        }
    
        /*
         * Complete login process including
         * Get user information
         * Set up CallApi object
         * update the user
         */
        async function completeLogin() {
            data.userInfo = new UserInfo({
                accessToken: data.implicitGrant.accessToken,
                workingUpdateF: workingUpdate
            });
            const ok = await data.userInfo.getUserInfo();
            if (ok) {
                data.callApi = new CallApi({
                    accessToken: data.implicitGrant.accessToken,
                    apiBaseUrl: data.userInfo.defaultBaseUrl
                });
                if (logLevel === 0) {
                    htmlMsg(
                        `<p><b>${data.userInfo.name}</b><br/>` +
                            `${data.userInfo.email}<br/>` +
                            `${data.userInfo.defaultAccountName}</p>`
                    );
                } else {
                    htmlMsg(
                        `<p><b>OAuth & UserInfo Results</b><br/>` +
                            `Name: ${data.userInfo.name}` +
                            ` (${data.userInfo.userId})<br/>` +
                            `Email: ${data.userInfo.email}<br/>` +
                            `Default account: ${data.userInfo.defaultAccountName}` +
                            ` (${data.userInfo.defaultAccount})<br/>` +
                            `Base URL: ${data.userInfo.defaultBaseUrl}</p>`
                    );
                }
            } else {
                // Did not complete the login
                $("#login").removeClass("hide");
                const errHtml = `<p class="text-danger">${data.userInfo.errMsg}</p>`;
                htmlMsg(errHtml);
            }
            return ok;
        }
    
        /*
         * Login button was clicked
         */
        let login = async function loginf(event) {
            $("#login").addClass("hide");
            await data.implicitGrant.login();
        };
        login = login.bind(this);
    
        // Mainline
        let data = {
            implicitGrant: null,
            userInfo: null,
            callApi: null,
            loggedIn: () => $("#doit").removeClass("hide")
        };
    
        // The mainline
        if (usingHttps()) {
            adjustRows();
            data.implicitGrant = new ImplicitGrant({
                workingUpdateF: workingUpdate
            });
    
            window.addEventListener("message", messageListener);
            $("#btnOauth").click(login);
            $("#btnDoit").click(doit);
        }
    });
    
    
    
      [1]: https://developers.docusign.com/platform/single-page-applications-cors/
      [2]: https://developers.docusign.com/docs/esign-rest-api/how-to/request-signature-in-cors-app-embedded/