Well, I solved this.
Sort of.
My solution was to forgo the use of a @RemoteAction
and instead use Salesforce's REST services.
For those following at home home, there are two ways to do this:
1. The easy way, use JSForce.
2. The stupid way (which I ended up using for reasons), roll your own XmlHttpRequest
calls.
Both are essentially the same (behind the scenes JSForce uses XmlHttpRequest
to do its thing), so the limit seems to be 25MB.
EDIT: For the adventurous, here's some boilerplate code.
let targetUrl = baseUrl + '/services/data/v37.0/sobjects/ContentVersion';
let xhr = new XMLHttpRequest();
xhr.open('POST', targetUrl, true);
xhr.setRequestHeader('Accept', '*/*');
xhr.setRequestHeader('Authorization', 'Bearer ' + sessionId);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = myCallbackFunction;
xhr.send(JSON.stringify({PathOnClient : fileName, VersionData : base64data});
EDIT September 2019
While searching for a solution to a similar problem, I encountered this question, and can now add functionality to allow uploading large files (>25MB).
Essentially, the best thing to do would be to use a multipart/form-data
request. However if you blithely do the following you'll encounter a problem:
let formData = new FormData()
let metadata = {
PathOnClient: fileName,
ContentDocumentId: docId,
ReasonForChange : changeReason
};
formData.append('entity_content', JSON.stringify(metadata));
formData.append('VersionData', fileField.files[0]);
...
xhr.send(formData);
You'll get a request built something like this:
--boundary_string
Content-Disposition: form-data; name="entity_content";
{
"ContentDocumentId" : "069XXXXXXXXXXXX",
"ReasonForChange" : "Marketing materials updated",
"PathOnClient" : "Q1 Sales Brochure.pdf"
}
--boundary_string
Content-Type: application/octet-stream
Content-Disposition: form-data; name="VersionData"; filename="Q1 Sales Brochure.pdf"
Binary data goes here.
--boundary_string--
Now that is very similar to Salesforce's example in the docs except that it's missing a crucial line: Content-Type: application/json
in the first part.
You'll get an error like so: {message: "Multipart message must include a non-binary part", errorCode: "INVALID_MULTIPART_REQUEST"}
After much searching and anguish it turns out that this isn't Salesforce's fault! It's a general problem with the FormData
object.
The solution is to generate your own request and send it. Like so:
<input type="file" #fileField name="fileName" />
let boundary = '----boundaryString';
...
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
xhr.send(new Blob([this.getRequestDataPart1(boundary), this.fileField.files[0], this.getRequestDataPart2(boundary)]));
getRequestDataPart1(boundary: string): string{
let data =
`--${boundary}
Content-Disposition: form-data; name="entity_content";
Content-Type: application/json
{
"ContentDocumentId" : "069XXXXXXXXXXXX", //leave blank for new ContentDocument
"ReasonForChange" : "Marketing materials updated", // not actually required
"PathOnClient" : "${this.fileName}"
}
--${boundary}
Content-Type: ${this.mimeType}
Content-Disposition: form-data; name="VersionData"; filename="${this.fileName}"
`;
return data;
}
getRequestDataPart2(boundary: string): string{
let data =
`
----${boundary}--
`;
return data;
}
I really hope that this helps someone. Maybe me in a year's time.