Fetch api can't be used for uploading Confluence files

Hi there!

I’m working on a new app, but I’m struggling very hard with the fetch api.
I’m trying to upload a png file to a Confluence page using the PUT REST api endpoint.

So far, I’ve been hitting multiple roadblocks.

1. Problem with wrapped API call
When I wrote my first piece of code using node-fetch and ran it locally, I managed to get everything working with the form-data library for handling the multipart handling.

When I tried the same snippet in a Confluence Forge event trigger, I got errors stating that the file handle couldn’t be cloned (when trying to read from a file) or function() {} could not be cloned (when reading from an ArrayBuffer).

By creating the multipart body myself, I was able to get ahold of these cloning-related errors - but this introduces a lot of hassle in the code base.

2. Header Content-Type multipart is being rewritten as application/json

More problematic is the fact that I kept getting error 415 (unsupported media) while trying to upload the file. Using a plethora of console.log statements, I couldn’t find the cause. The same code running locally with node-fetch worked like a charm.

By using Pipedream, I was able to set up an endpoint and inspect the data being send over there in detail. It turned out that while I set the content-type to multipart, it ends up being application/json - causing the 415 unsupported media of course.

Since this is totally out of my reach to fix, I’m hoping to find a solution over here :slight_smile:
Thanks a lot!

Tom

let base_url = '';
let pageId = '';
//...
let url = `${base_url}/wiki/rest/api/content/${pageId}/child/attachment?status=current`;
let upload_options = {
    method: 'PUT',
    headers: {
        'X-Atlassian-Token': 'nocheck',
        'Accept': 'application/json',
        'Content-Type': `multipart/form-data; boundary=${boundary}`,
        'Authorization': 'Basic CREDENTIALS'
    },
    body: body
}
fetch(url, upload_options).then((upload_result) => {
// No difference when using a product specific version of fetch
//api.asApp().requestConfluence(url, upload_options).then((upload_result) => {
    console.log("Upload complete");
});

PS: I’m using the fetch with API token for now until the permission problem regarding the Forge user account created is resolved - just a temporary solution

1 Like

Hey @TomMoors,

Sorry you had to experience this issue :frowning:

We are aware of this and working on it at the moment. I cannot give a timeline as to when this will be resolved but here is where you can track it: [FRGE-114] Cannot use multipart/form-data APIs with Forge - Ecosystem Jira.

Thanks for reporting this and happy Forging!

Hi @TomMoors,
it is possible to specify the content-type since a few days. If you now create the form-data-payload manually, you can upload attachments in Confuence.
With this source code we are able to upload attachments.

SomeAction.js

import { v4 as uuidv4 } from 'uuid';
import { requestConfluence } from "@forge/bridge";
import {
    generateFormData
} from "./index";
.....
let dataForm = {
	id : "some-id",
	name : "some-name",
	...
}
let attachmentName = dataForm.id + ".dataform.json"
let requestUrl = `/wiki/rest/api/content/${pageId}/child/attachment`;
let boundary = "DataForms-" + uuidv4();
const json = JSON.stringify(dataForm);
let formData = generateFormData(attachmentName, json, dataForm.name, boundary);
requestConfluence(requestUrl,
		{
				method: "PUT",
				body: formData,
				headers: {
						'Accept': 'application/json',
						'X-Atlassian-Token': 'no-check',
						'content-type': 'multipart/form-data; boundary=' + boundary
				},
				credentials: 'include'
		}).then(response => { .....
...

and the index.js

...
export const generateFormData = (attachmentName, dataFormJson, comment, boundary) => {
    let newLine = "\r\n";
    // encapsulation boundary two hyphen characters + the boundary param https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
    let boundaryMarker = "--";
    let formData = boundaryMarker + boundary + newLine +
        "Content-Disposition: form-data; name=\"file\"; filename=\"" + attachmentName + "\"" + newLine +
        "Content-Type: application/dataforms" + newLine + newLine +
        dataFormJson + newLine +
        boundaryMarker + boundary + newLine +
        "Content-Disposition: form-data; name=\"fileName\"" + newLine + newLine +
        attachmentName + newLine +
        boundaryMarker + boundary + newLine +
        "Content-Disposition: form-data; name=\"title\"" + newLine + newLine +
        attachmentName + newLine +
        boundaryMarker + boundary + newLine +
        "Content-Disposition: form-data; name=\"comment\"" + newLine + newLine +
        comment + newLine +
        boundaryMarker + boundary + newLine +
        "Content-Disposition: form-data; name=\"minorEdit\"" + newLine + newLine +
        "true" + newLine +
        boundaryMarker + boundary + boundaryMarker + newLine
    return formData
}
....

I hope the source code helps.

3 Likes

Thanks Andreas!