docusign / native-ios-sdk

A set of official UI components along with programmable client libraries that enable developers to integrate their products with DocuSign’s signature service API on iOS
https://docusign.github.io/native-ios-sdk/documentation/docusignsdk/
Other
28 stars 27 forks source link

Unable to Start Embedded Signature. Blank Screen + ProgressView. #171

Open jonathansds opened 2 months ago

jonathansds commented 2 months ago

Hi, good morning! I need some help with the embedded signature. I am using Swift, XCode 15.4, simulator with iOS 17.5. I am creating the envelope with the API:

EnvelopesAPI.envelopesPostEnvelopes(accountId:body:)

but them I am unable to start the embedded signature with:

DSMEnvelopesManager().presentCaptiveSigning(withPresenting:envelopeId:recipientUserName:recipientEmail:recipientClientUserId:animated:)

After calling the method above, just a blank screen and a ProgressView appear, nothing else. I enabled logs on DocuSign platform and after going through them (in attachments) I noticed that the below call:

POST https://demo.docusign.net/restapi/v2.1/accounts/<account-id>/envelopes/<envelope-id>/views/recipient

fails with error:

{"errorCode":"UNKNOWN_ENVELOPE_RECIPIENT","message":"The recipient you have identified is not a valid recipient of the specified envelope."}

I already added an extra call to verify the created recipients before starting the embedded signature but still, I couldn't identify any reason on why the recipient would not be válid. In the attached logs, there's also the result for the extra call getting all the receipients for the envelope.

I am using the below code and call sequence:

1) Start DocuSign Session:

            let userSession = try await DSMManager.login(
                withAccessToken: accessToken, // Get this from backend. Same is used for Android and it works fine.
                accountId: apiAccountId, // ********-****-****-****-************ double checked. 
                userId: userAccountId, // ********-****-****-****-************ double checked. 
                userName: userAccountName, // 4FreedomHub Developer "Directly from DocuSign platform"
                email: userAccountEmail, // developer.********.***@4freedomhub.com "Directly from DocuSign platform"
                host: URL(string: baseURI)!, // https://demo.docusign.net/restapi
                integratorKey: integrationKey, // ********-****-****-****-************
                refreshToken: refreshToken, // nil
                expiresIn: Date(timeIntervalSinceNow: TimeInterval(expireIn))  // Get this from backend
            )

After this call, no error happens but I noticed that inside userSession object, accountId is empty and if I try to use userSession.host the SDK crash. For this reason, I am unable to directly use userSession.host neither userSession.accountId in the creation of the envelope but I am sending the exact same values. THIS IS THE FIRST PROBLEM I BELIEVE I NEED A FIX FOR.

2) Composing Envelope:

        let recipientPermuted = DSAPIInPersonSigner(
            clientUserId: recipientUserId, // 2SIVn1ybGDQOBNQtJyLX9qhgpxn1
            hostEmail: userSession.email, // developer.********.***@4freedomhub.com
            hostName: userSession.accountName, // 4FreedomHub Developer
            recipientId: "1",
            routingOrder: "1"
            signerEmail: recipientEmail, // signer.test.****.********@4freedomhub.com
            signerName: recipientName, // Jonathan Santos
            status: "SENT",
            tabs: DSAPITabs(
                signHereTabs:[DSAPISignHere(
                    anchorCaseSensitive: false,
                    anchorString: signatureAnchor, // "{{permuted_authentication}}"
                    documentId: "1",
                    pageNumber: "1",
                    recipientId: "1"
                )]
            )
        )

        let recipientPermuting = ...
        let recipientAuthorizer = ...

        let pdfData = try Data(contentsOf: permutationFile)
        let document = DSAPIDocument()
        document.documentId = documentId
        document.documentBase64 = pdfData.base64EncodedString()
        document.name = permutationFile.deletingPathExtension().lastPathComponent
        document.fileExtension = "pdf"

        let apiEnvelope = DSAPIEnvelopeDefinition(
            documents: [document],
            emailSubject: envelopeSubject, // "Permuta de Horário de Serviço"
            recipients: DSAPIRecipients(
                inPersonSigners: [recipientPermuted, recipientPermuting, recipientAuthorizer]
            ),
            signerCanSignOnMobile: true,
            status: "SENT"
        )

        DSClientAPI(
            basePath: baseURI,
            customHeaders: ["Authorization": "Bearer \(hostAccountInfo.accessToken)"]
        )

        EnvelopesAPI.envelopesPostEnvelopes(
                accountId: apiAccountId, // ********-****-****-****-************ 
                body: apiEnvelope
        ) { [weak self] envelopeSummary, error in
                if let error = error {
                    ...
                    return
                }
                if let errorDetails = envelopeSummary?.errorDetails {
                    ...
                    return
                }
                guard let envelopeId = envelopeSummary?.envelopeId else {
                    ...
                    return
                }
                // envelopeId of created envelope. 786b3e9b-9b8a-4219-b4b1-a577824893a4
            }

3) Trying to start the Embedded signature flow

        guard let rootViewController = await getRootViewController() else {
            ...
            return
        }
               DSMEnvelopesManager().presentCaptiveSigning(
                    withPresenting: rootViewController,
                    envelopeId: envelopeId, // 786b3e9b-9b8a-4219-b4b1-a577824893a4
                    recipientUserName: recipientUserName, // Jonathan Santos
                    recipientEmail: recipientEmail, // signer.test.****.********@4freedomhub.com
                    recipientClientUserId: recipientClientUserId, // 2SIVn1ybGDQOBNQtJyLX9qhgpxn1
                    animated: true
                )

As I mentioned, I already added an extra call before starting the Embedded Signature flow, retrieving all recipients for the created envelope, no errors were found and I used the exact data received from this call (which is the same I am sending anyways) to start the signature flow but the error persists (blank screen and ProgressView. Nothing else).

I also noticed that if, for the presentCaptiveSigning call, I swap the recipientUserName and recipientEmail and send the HOST name and HOST email instead, the inicial screen for the signature flow appears but it appears behind the ProgressView which is not dismissed neither I found a way to dismiss it. It was just an experiment but I have doule checked the host values and signer values on both the envelope creation and from the get recipients call before the signature. DOCUSIGN_LOGS_06-09-FILTERED.zip

smd9788 commented 2 months ago

Hi @jonathansds ,

1) Are you seeing the DSMAccountInfo response to your DSMManager.login() call? I believe this should contain the user's account Id's: https://docusign.github.io/native-ios-sdk/documentation/docusignsdk/dsmaccountinfo/

2/3) Since you are using an In Person Signer, you will need to use the Host's information instead of the signer's information in your create view request:

https://developers.docusign.com/docs/esign-rest-api/how-to/send-envelope-to-in-person-signer/

It sounds like you figured this part out, but are still having issues with the embedded view. To troubleshoot this it would be very helpful to look a network trace (HAR file) and the console logs. Since a HAR might contain sensitive info, I would suggest reaching out to Docusign support. Here's the link to open a support case with DocuSign: https://support.docusign.com/s/articles/How-Do-I-Open-a-Case-in-the-DocuSign-Support-Center?language=en_US

jonathansds commented 2 months ago

Hi @smd9788 , Thanks for taking the time to look at the issue. Appreciate it.

  1. Yes yes, as shown in the code I provided, I have the response from DSMManager.login() call saved in let userSession. The call is successful and the DSMAccountInfo is there but the accountId is empty and the host also.

As I provided in the code, I make the login call and save the result in a property called userSession:

            let userSession = try await DSMManager.login(
                withAccessToken: accessToken, // Get this from backend. Same is used for Android and it works fine.
                accountId: apiAccountId, // ********-****-****-****-************ double checked. 
                userId: userAccountId, // ********-****-****-****-************ double checked. 
                userName: userAccountName, // 4FreedomHub Developer "Directly from DocuSign platform"
                email: userAccountEmail, // developer.********.***@4freedomhub.com "Directly from DocuSign platform"
                host: URL(string: baseURI)!, // https://demo.docusign.net/restapi
                integratorKey: integrationKey, // ********-****-****-****-************
                refreshToken: refreshToken, // nil
                expiresIn: Date(timeIntervalSinceNow: TimeInterval(expireIn))  // Get this from backend
            )

When trying to access the userSession.accountId it returns an empty String and when trying to access the userSession.host it throws an exception.

  1. I am not manually calling the CreateRecipientView API myself (I did it a couple times trying to find out the problem but it's being created/called behind the scenes when I call DSMEnvelopesManager().presentCaptiveSigning.

Indeed, as a test, I called DSMEnvelopesManager().presentCaptiveSigning sending the host information and then I can see the initial signing screen of the embedded signing flow, but then the ProgressView is still there and won't dismiss.

I would like to point out that if using the host information on DSMEnvelopesManager().presentCaptiveSigning is the correct way to do it, then it's very misleading as in the documentation (https://developers.docusign.com/docs/mobile-sdks/ios-sdk/how-to/request-signature-embedded/) says:

{SIGNER_EMAIL}  A string value for the email address where the signer will receive a notification of the signing request.
{SIGNER_NAME}   A string value for the full name of the signer.

DSMEnvelopesManager().presentCaptiveSigning(withPresenting: self, 
    envelopeId: "{ENVELOPE_ID}", 
    recipientUserName:"{SIGNER_NAME}", 
    recipientEmail: "{SIGNER_EMAIL}", 
    recipientClientUserId: "{RECIPIENT_CLIENT_USER_ID}", 
    animated: true, 
    completion: { viewController, error  in
    guard error == nil else {
        print(error)
        return
    }
})

Not only the documentation says to use the SIGNER information but also the method parameters' names mention RECIPIENT name and email. Nothing seems to point out that we should send the host information.

Now, coming back to the issue, after I update my "DSMEnvelopesManager().presentCaptiveSigning" call to the below:

     guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
          let window = windowScene.windows.filter({$0.isKeyWindow}).first,
          var rootViewController = window.rootViewController else {
        return nil
    }

        DSMEnvelopesManager().presentCaptiveSigning(
            withPresenting: rootViewController,
            envelopeId: envelopeId,
            recipientUserName: hostAccountInfo.userName, // HOST USER NAME
            recipientEmail: hostAccountInfo.email, // HOST EMAIL
            recipientClientUserId: recipientClientUserId,
            animated: true
        ){ [weak self] _, error in
            if let error = error {
                ...
            }
            ...
        }

I now see the initial signing screen but with a ProgressView on top that does not dimiss so I am unable to proceed (see image attached). Also, the problem with the "host" and "accountId" not being available inside "DSMAccountInfo" persists.

Thanks for taking the time to help me out. I will provide you with the information from the HAR file (don't worry, I will omit sensitive information) and the console logs in the next comment.

Again, I appreciate the help. Sorry, I don't know how to scale down the screenshot here :/

Simulator Screenshot - iPhone 15 Pro - 2024-09-09 at 08 09 08

jonathansds commented 2 months ago

Unfortunately, there's no active web sessions to be inspected when the issue happens so I am unable to generate a HAR file (see image below).

As for logs, I enabled debug logs on DSMManager.setLoggingLevel. Here is the output:

Created new private queue context: <NSManagedObjectContext: 0x6000039cd380> → Saving <NSManagedObjectContext (0x6000039cd380): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039c0dd0> → Saving <NSManagedObjectContext (0x6000039c0dd0): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Created new private queue context: <NSManagedObjectContext: 0x6000039cd380> Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Saving <NSManagedObjectContext (0x6000039cd380): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039cd110> → Saving <NSManagedObjectContext (0x6000039cd110): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039cda00> → Saving <NSManagedObjectContext (0x6000039cda00): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). Created new private queue context: <NSManagedObjectContext: 0x6000039c4ea0> → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Saving <NSManagedObjectContext (0x6000039c4ea0): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039be630> → Saving <NSManagedObjectContext (0x6000039be630): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039cd110> → Saving <NSManagedObjectContext (0x6000039cd110): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039d0340> → Saving <NSManagedObjectContext (0x6000039d0340): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039ca080> → Saving <NSManagedObjectContext (0x6000039ca080): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039d0340> → Saving <NSManagedObjectContext (0x6000039d0340): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread Created new private queue context: <NSManagedObjectContext: 0x6000039d0340> → Saving <NSManagedObjectContext (0x6000039d0340): saveWithBlock:completion:> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'saveWithBlock:completion:' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Save Parents? YES → Save Synchronously? NO Context 'DSM_MagicalRecord Root Saving Context' is about to save: obtaining permanent IDs for 2 new inserted object(s). → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on a background thread → Saving <NSManagedObjectContext (0x6000039095f0): DSM_MagicalRecord Default Context> on the main thread → Save Parents? YES → Save Synchronously? YES → Saving <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on the main thread → Save Parents? YES → Save Synchronously? YES → Finished saving: <NSManagedObjectContext (0x6000039096c0): DSM_MagicalRecord Root Saving Context> on the main thread

Screenshot 2024-09-09 at 08 49 10
jonathansds commented 2 months ago

Hi @smd9788 , I tested changing the signer types to DSAPISigner instead of DSAPIInPersonSigner. Just by changing the signer type, the issue went back to be full blank screen again with nothing else appearing. But then, I changed back the DSMEnvelopesManager().presentCaptiveSigning call to send the signer information (name and email) instead of the host and the signature flow loaded successfully.

I haven't tested actually completing the signature yet (I went as far as actually drawing the signature but didnt click to confirm) as I need to fix some details with language and anchor string yet but should be fine I would say.

Now I would say the only remaining issue for now is the DSMAccountInfo response not containing accountId and host.

Thank you!

jonathansds commented 2 months ago

Any update on the issue?

ashokds commented 2 months ago

Hi @jonathansds -

Thanks for adding all the details to this issue.

DSAPIInPersonSigner should work in the similar manner as the DSAPISigner -- it's a one of the most used setup to get signatures. This issue has been prioritized and will provide the update in coming days, meanwhile, please try the following:

On the same envelope, are you able to perform the Captive-signing (also called Embedded-signing) for the IPS on desktop-web after requesting the signing URL? There is a Postman collection that has API to get signing URL.

Once you create a captive-signing envelope with {{url}}/accounts/{{account}}/envelopes endpoint, example:

{
    "envelopeId": "2398e467-ca5a-43f1-ba1f-285168dead7f",
    "uri": "/envelopes/2398e467-ca5a-43f1-ba1f-285168dead7f",
    "statusDateTime": "2024-09-18T17:06:13.9100000Z",
    "status": "sent"
}

Another call could be made to fetch the Recipient Specific Signing URL with the endpoint {{url}}/accounts/{{account}}/envelopes/2398e467-ca5a-43f1-ba1f-285168dead7f/views/recipient to get a url that can be used to load signing session for that particular Recipient without authentication:

{
    "url": "https://demo.docusign.net/Signing/MTRedeem/v1/cccfe2a2-eecb-4620-b892-3b6df402b766?slt=eyJ0eXAiOiJNVCIsImFsZyI6IlJTMjU2Iiwia2lkIjoiNjgxODVmZjEtNGU1MS00Y2U5LWFmMWMtNjg5ODEyMjAzMzE3In0.[redacted]-0E2NxI.qcvupgftB_VUf4wTbWr63TbMY2PqBLdHvs1P2jfXK5xgUXK2uxs6R9gwI8m5UHIVUcuRjWAJa2ZhgjYeDg_4v4I9Buje-ju6PKwF5uE68yjlMvzNjRbNaDfQped0QA3uEVaPxXFJHmCT80RYJQgVgzXqDQQt8yA2ZBXxWInHgbtnasZKvMu1INDTaPv_Lt1qYpBa_k7v1RHtwkwcUWPc1ZUkM1aef2ApJ7i8P9ULpKEWNkCgIJ4F0Fk1mNNXVDQjCaB9nIl4V3-SGVm2ofWniqFCjXSWk2yH1NdfcdFlS6a-sF7kglXFkoWFCkYeMzOo6YrU_ac5DKArhwQAy_JR5Q"
}

^ this URL can also be generated on the backend and passed to the app -- it eliminates the use of tokens being sent to the apps. Please find additional details on authentication-less signing and life-span of the URL: Embedded-Signing Details -- Thanks.

jonathansds commented 2 months ago

Hi @ashokds , Thanks for taking the time to look into this. unfortunately, we have a tight schedule on our side with the iOS development so for now, as a workaround we are using the DSAPISigner instead and manually (hardcoded) the information that is missing from DSMAccountInfo. I will keep tracking this issue and will happily test the fix once you guys provide it but won't be able to provide you with more details apart from what I have already provided due to our tight schedule on the release of the iOS version of our app. Thanks for understanding.