React集成DocuSign Focus View加载无限循环及CSP错误求助
DocuSign Focus View嵌入React应用时CSP错误导致无限加载
问题背景
在React应用中集成DocuSign Focus View,采用Node.js的DocuSign SDK通过Express服务器发起API请求。Postman可正常获取URL,直接访问链接能打开表单,但在React应用中嵌入该URL时,Focus View加载图标陷入无限循环,控制台出现Content Security Policy(CSP)错误。已尝试服务器端禁用CSP、在index.html添加meta标签,参考DocuSign React示例应用也无进展。
React组件代码
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;
API请求代码
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", }); } });
辅助函数代码
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; };
解决步骤
1. 修正frameAncestors配置
当前frameAncestors设置不完整,需包含React应用完整域名(含端口),并覆盖DocuSign所有必要子域:
- 在
makeRecipientViewRequest函数中,更新frameAncestors:viewRequest.frameAncestors = [ "https://localhost:5173", // 替换为你的React应用实际地址 "https://*.docusign.com", "https://*.docusign.net" ]; - 删除
createRecipientView调用中外层重复的frameAncestors参数,避免配置冲突。
2. 配置React应用的CSP规则
在React的public/index.html中添加正确的CSP meta标签:
<meta http-equiv="Content-Security-Policy" content=" frame-src https://*.docusign.com https://*.docusign.net; script-src https://*.docusign.com 'unsafe-inline' 'unsafe-eval'; style-src https://*.docusign.com 'unsafe-inline'; img-src https://*.docusign.com data:; connect-src https://*.docusign.com https://*.docusign.net ">
3. 修复React组件的依赖与清理逻辑
更新useEffect的依赖数组,并添加实例清理逻辑,避免内存泄漏:
useEffect(() => { let signingInstance; const loadDocuSign = async () => { try { const docusign = await window.DocuSign.loadDocuSign(integrationKey); const url = `${docUrl}&output=embed`; signingInstance = docusign.signing({ url, displayFormat: 'focused' }); signingInstance.mount('#agreement'); } catch (ex) { console.error(ex); } }; loadDocuSign(); // 组件卸载时清理实例 return () => { if (signingInstance) { signingInstance.unmount(); } }; }, [integrationKey, docUrl]); // 添加依赖,确保参数变化时重新加载
4. 验证信封状态与参数一致性
确保makeEnvelope中env.status = "sent",且signerClientId在创建信封和收件人视图时完全一致。
内容的提问来源于stack exchange,提问作者ubi
相关产品推荐
相关产品推荐

