1

The task:

We have over 1500 .msg files in Salesforce, which are Outlook containers for Mail conversations plus attachments. The idea is to build a script, which downloads all files, extract each attachment and Mail conversation and uploading the files to Salesforce with the corresponding opportunities. It is convenient for us to use JSForce at the moment. Querying all the files in the database works fine. But VersionData is path to the actual document, not the document itself.

The Problem:

Here is the important part of the code:

conn.queryAll(oneLine`
  SELECT ContentDocumentId,
    ContentDocument.Title,
    ContentDocument.FileExtension,
    ContentDocument.LatestPublishedVersion.VersionData,
    ContentDocument.LatestPublishedVersion.Id
  FROM ContentDocumentLink
  WHERE ContentDocument.FileExtension = 'msg' AND
    LinkedEntityId = '${teamId}'
`)
  .then(result => {
    result.records.map(document => {
      if (!document.ContentDocument.LatestPublishedVersion) {
        /* error handling */
      }
      const filename =
        document.ContentDocumentId + '.' +
        document.ContentDocument.FileExtension

      const dataPath = document.ContentDocument.LatestPublishedVersion.VersionData
      conn.request(dataPath)
        .then(data => {
          fs.writeFile(`./files/${filename}`, data, () => {
            console.log(`downloaded ${filename}`)
            /* Do stuff */
          })
        })
        .catch(requestError => {
          fs.appendFile(logNames.requestErrors, `${requestError}\n\n\n\n\n`,() => {})
        })
    })
...

It will successfully download files, but they seem to be corrupted, because none of them can be viewed by any program suitable to open .msg files.

I had another approach like this:

...
  .then(result => {
    result.records.map(document => {
      if (!document.ContentDocument.LatestPublishedVersion) {
        /* error handling */
      }
      const filename =
        document.ContentDocumentId + '.' +
        document.ContentDocument.FileExtension

      const fileDesc = fs.createWriteStream(`./files/${filename}`)
        .on('error' , error => {
          console.error(error)
        })
      conn
        .sobject('ContentVersion')
        .record(document.ContentDocument.LatestPublishedVersion.Id)
        .blob('VersionData')
        .pipe(fileDesc)
        .on('error', error => {
          console.error(error)
        })
        .end(() => {
          console.log('Closing FD')
          fileDesc.close()
        })
    })

Here, nothing happend. Nothing was downloaded and no error was printed or logged.

The documentation seems to be not up-to-date or at leat not helpful to me. I appreciate every help i can get.

  • Yes it did, but getting the actual HTTP requests to work was kind of a pain. Thank you very much! – Eleos91 Mar 11 '20 at 10:39
0

with the helpful link from identigral, i got it to work. With the token generated by jsfroce and using node-fetch, it's not complicated at all.

So my solutions to download a bunch of VersionData Documents is as follows:

const jsforce = require('jsforce')
const { oneLine } = require('common-tags')
const fs = require('fs')
const fetch = require("node-fetch")

conn.queryAll(oneLine`
  SELECT ContentDocumentId,
    ContentDocument.FileExtension,
    ContentDocument.LatestPublishedVersion.VersionData,
    ContentDocument.LatestPublishedVersion.Id
  FROM ContentDocumentLink
  WHERE ContentDocument.FileExtension = 'msg' AND
    LinkedEntityId = '${teamId}'
`)
  .then(result => {
    console.log(result.records.length)
    result.records.map(document => {
      if (!document.ContentDocument.LatestPublishedVersion) {
        /* error handling */
        return
      }
      const filename =
        document.ContentDocumentId + '.' +
        document.ContentDocument.FileExtension

      const dataPath = document.ContentDocument.LatestPublishedVersion.VersionData
      headers = {
        'Authorization': 'Bearer ' + conn.accessToken,
        'Content-Type': 'blob',
      }
      options = {
        method: 'GET',
        headers: headers,
      }
      fetch(conn.instanceUrl + dataPath, options)
        .then(result => {
          result.body
            .pipe(fs.createWriteStream(`./files/${filename}`))
        })
        .catch(error => {
          console.log(error)
        })
    })

...

It seems like jsforce request has a bug or something. Maybe requesting a blob and getting a string is the problem. I try to open an issue. I hope that some people won't have the same trouble as me.

Not the answer you're looking for? Browse other questions tagged or ask your own question.