How to PUT media on https://api.linkedin.com/mediaUpload/ with NodeJs - node.js

When I call the route https://api.linkedin.com/v2/assets?action=registerUpload with axios
I have a response like this
{
"value": {
"uploadMechanism": {
"com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest": {
"uploadUrl": "https://api.linkedin.com/mediaUpload/C4E22AQGSNx_ko_tzLw/feedshare-uploadedImage/0?ca=vector_feedshare&cn=uploads&m=AQK-VkaHKm7-NQAAAXMad6RKlbsQGW9Vi3JI0iFnlEKhTdhVcSZxyxUJ5g&app=17412253&sync=1&v=beta&ut=0MQzOzxqQ7m9k1",
"headers": {
"media-type-family": "STILLIMAGE"
}
}
},
"asset": "urn:li:digitalmediaAsset:C4E22QSGSNx_ko_tzLw",
"mediaArtifact": "urn:li:digitalmediaMediaArtifact:(urn:li:digitalmediaAsset:C4E22AQGSNx_ko_tzLw,urn:li:digitalmediaMediaArtifactClass:feedshare-uploadedImage)"
}
}
I want to upload an image from my server with the upload link
var newFile = fs.createReadStream(__dirname+"/temp/lion.png");
const form_data = new FormData();
form_data.append('file', newFile);
const request_config = {
headers: {
'Authorization': `Bearer ${access_token}`,
"Content-Type": "multipart/form-data"
},
data: {content : form_data['_streams']['1']}
};
const res = await axios.put('https://api.linkedin.com/mediaUpload/C4E2OIQNQE5ILcQCU_lLA/feedshare-uploadedImage/0?ca=vector_feedshare&cn=uploads&m=AQIy5jpkZ0ut2AAAAXMadpS8A97cK9wOSjzagaNHo97bRPCYVZt7f5E4yQ&app=17411153&sync=1&v=beta&ut=2JZ18aO4E6m9k1', form_data, request_config);
But I have this response from the server
"Error: Request failed with status code 400\n at createError (/home/node/app/node_modules/axios/lib/core/createError.js:16:15)\n at settle (/home/node/app/node_modules/axios/lib/core/settle.js:17:12)\n at IncomingMessage.handleStreamEnd (/home/node/app/node_modules/axios/lib/adapters/http.js:236:11)\n at IncomingMessage.emit (events.js:198:15)\n at endReadableNT (_stream_readable.js:1139:12)\n at processTicksAndRejections (internal/process/task_queues.js:81:17)"

7 Months old question, but still an issue today, was trying to figure this thing for few hours.
Could have been much easier if only Linkedin would add (like a normal API) the information they expect to get, like the Content-Type header, or what encoding.
I have a URL I need to upload as an image, so I have do some extra steps, but the general idea is you need to include the Content-Type of your image, use the URL they give you to send the file, and get the file as ArrayBuffer, as well as not forget to use the PUT method.
to get ArrayBuffer, and get the content type of the file:
axios.get(imageUrl, {responseType: 'arraybuffer'}).then((imageData) => {
const contentType = imageData.headers['content-type'];
//The ArrayBuffer is at imageData.data
})
NOTE- Don't forget to include supportedUploadMechanism: ['SYNCHRONOUS_UPLOAD'], so the it will upload synchronously, and you won't have to send another request to make sure the upload was done successfully.
const registerUploadRequest = {
owner: this.owner,
recipes: ['urn:li:digitalmediaRecipe:feedshare-image'],
serviceRelationships: [
{
identifier: 'urn:li:userGeneratedContent',
relationshipType: 'OWNER',
},
],
supportedUploadMechanism: ['SYNCHRONOUS_UPLOAD'],
};
After I got the image data I need, I send request to /assets?action=registerUpload so I can get the id of the asset, as well as the URL I need to upload it to:
return this.post('/assets?action=registerUpload', {registerUploadRequest}).then((result) => {
const url = result.value.uploadMechanism['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest'].uploadUrl,
assetId = result.value.asset.replace('urn:li:digitalmediaAsset:', '');
})
Note - we need the id to use the asset after upload.
After I finally got all the data I need, I simply PUT the ArrayBuffer to the url given, with the correct content-type.
return axios.put(url, imageData.data, {
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': contentType,
},
})
And you are done.
IMPORTANT - they do not send any response in the data object, if the request was successful the image uploaded, if it failed, it didn't.

Related

form-data request body is empty

I'm trying to send an image and some data from an API to another. The image is stored in memory with multer. But when I want to send it, the body is just empty. I tried the same request with postman and it worked perfectly.
postman test
postman test image
server test
server test image
Here is some code. I removed some of it so you can read it better
export const saveImage = async ({ image, name, folder, options }: { image: any, name?: any, folder: string, options?: any }) => {
try {
const fd = new FormData();
fd.append("image", image.buffer, image.originalname);
if(options) {
fd.append("options[resize][height]", options?.resize?.height);
fd.append("options[resize][width]", options?.resize?.width);
}
if(name) fd.append("name", name);
fd.append("folder", folder);
fd.append("servideId", IMAGES_ID);
fd.append("serviceSecret", IMAGES_SECRET);
console.log(fd)
const formHeaders = fd.getHeaders();
const request = await axios.post(`${IMAGES_URL}/api/images`, {
headers: formHeaders,
body: fd
});
return request.data.id;
} catch (error) {
const { response } = error;
console.log(response.request.data)
if(error?.response?.data?.error) {
throw { statusCode: error.response.status, message: error.response.data.error }
}
console.error("Images API", error);
throw new InternalError("Something gone wrong");
}
}
When I log the FormData, I can see in _streams, the data that I'm sending, but the Images API receives an empty body.
FormData screenshot
If you need more information tell me, please! Thank you
The axios API for the post method is: axios.post(url[, data[, config]]). The second argument must always be the data you send along.
In your case axios thinks { headers: formHeaders, body: fd } is the body and the request ends up being application/json. To send a file with data using axios in Node.js, do the following:
const response = await axios.post(`${IMAGES_URL}/api/images`, fd, {
headers: {
...formHeaders,
'X-Custom-Header': 'lala', // optional
},
});
Your question inspired me to turn this answer into an article — Send a File With Axios in Node.js. It covers a few common pitfalls and you'll learn how to send files that are stored as a Buffer or coming from a Stream.
With Axios, you can directly use the form data without having to deal with headers.
axios.post("/api/images", fd)
If you wish to modify headers at some point in the future, you should pass the formData to the `data` field instead of `body`.
axios.post("/api/images", { headers: formHeaders, data: fd })
Correction in comments.
It can also be done using the Axios API syntax.
axios({method: 'post', url: 'url', data: fd, headers: {} })
In the backend, multer will add your files to req.file instead of req.body, if you have properly configured it to do so.

Get image from Axios and send as Form Data to Wordpress API in a Cloud Function

What I'm trying to accomplish is using a Firebase Cloud Function (Node.js) to:
First download an image from an url (f.eg. from unsplash.com) using an axios.get() request
Secondly take that image and upload it to a Wordpress site using the Wordpress Rest API
The problem seems (to me) to be that the formData doesnt actually append any data, but the axios.get() request actually does indeed retrieve a buffered image it seems. Maybe its something wrong I'm doing with the Node.js library form-data or maybe I get the image in the wrong encoding? This is my best (but unsuccessfull) attempt:
async function uploadMediaToWordpress() {
var FormData = require("form-data");
var formData = new FormData();
var response = await axios.get(
"https://images.unsplash.com/photo-1610303785445-41db41838e3e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80"
{ responseType: "arraybuffer" }
);
formData.append("file", response.data);
try {
var uploadedMedia = await axios.post("https://wordpresssite.com/wp-json/wp/v2/media",
formData, {
headers: {
"Content-Disposition": 'form-data; filename="example.jpeg"',
"Content-Type": "image/jpeg",
Authorization: "Bearer <jwt_token>",
},
});
} catch (error) {
console.log(error);
throw new functions.https.HttpsError("failed-precondition", "WP media upload failed");
}
return uploadedMedia.data;
}
I have previously successfully uploaded an image to Wordpress with Javascript in a browser like this:
async function uploadMediaToWordpress() {
let formData = new FormData();
const response = await fetch("https://images.unsplash.com/photo-1610303785445-41db41838e3e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80");
const blob = await response.blob();
const file = new File([blob], "image.jpeg", { type: blob.type });
formData.append("file", file);
var uploadedMedia = await axios.post("https://wordpresssite.com/wp-json/wp/v2/media",
formData, {
headers: {
"Content-Disposition": 'form-data; filename="example.jpeg"',
"Content-Type": "image/jpeg",
Authorization: "Bearer <jwt_token>",
},
});
return uploadedMedia.data;
},
I have tried the last couple of days to get this to work but cannot for the life of me seem to get it right. Any pointer in the right direction would be greatly appreciated!
The "regular" JavaScript code (used in a browser) works because the image is sent as a file (see the new File in your code), but your Node.js code is not really doing that, e.g. the Content-Type value is wrong which should be multipart/form-data; boundary=----...... Nonetheless, instead of trying (hard) with the arraybuffer response, I suggest you to use stream just as in the axios documentation and form-data documentation.
So in your case, you'd want to:
Set stream as the responseType:
axios.get(
'https://images.unsplash.com/photo-1610303785445-41db41838e3e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80',
{ responseType: 'stream' }
)
Use formData.getHeaders() in the headers of your file upload request (to the /wp/v2/media endpoint):
axios.post( 'https://wordpresssite.com/wp-json/wp/v2/media', formData, {
headers: {
...formData.getHeaders(),
Authorization: 'Bearer ...'
},
} )
And because the remote image from Unsplash.com does not use a static name (e.g. image-name.jpg), then you'll need to set the name when you call formData.append():
formData.append( 'file', response.data, 'your-custom-image-name.jpeg' );
I hope that helps, which worked fine for me (using the node command for Node.js version 14.15.4, the latest release as of writing).

How do I upload a custom playlist cover image with Spotify API

I'm on a private node.js project.
On the server there should be an endpoint to update the cover image of a specific playlist. Inside this endpoint I've got this code:
let playlistID = '7fOfY.......G5RFK3z'; // ID of already created playlist
let imgFile = '/9j/4AAQSkZJRg.......AgICAg'; // data:image/jpeg;base64
let spotifyAccessToken = 'DHdhw3.......DHdfLS8'; // valid access token
let options = {
url: 'https://api.spotify.com/v1/playlists/' + playlistID + '/images',
headers: {
'Authorization': 'Bearer ' + spotifyAccessToken,
'Content-Type': 'image/jpeg'
},
body: imgFile
}
request.put(options,(error, response) => {
if(response.statusCode === 202) {
console.log('Upload cover');
} else {
console.log(JSON.stringify(response));
}
In the terminal it always fails and there is no cover image in Spotify. Anyone knows what is wrong? What can I do to fix this problem? https://developer.spotify.com/documentation/web-api/reference/playlists/upload-custom-playlist-cover/
EDIT:
Response object looks like this:
{"statusCode":400,
"body":{
"error": {
"status": 400,
"message": "Bad request."
}
},
"headers":{
"content-type": "application/json; charset=utf-8",
"cache-control": "private",
"max-age=0",
"access-control-allow-origin":"*",
"access-control-allow-headers":"Accept, App-Platform, Authorization, Content-Type, Origin, Retry-After, Spotify-App-Version, X-Cloud-Trace-Context",
"access-control-allow-methods":"GET, POST, OPTIONS, PUT, DELETE, PATCH",
"access-control-allow-credentials":"true",
"access-control-max-age":"604800",
"content-length":"72",
"date":"Fri, 24 Jan 2020 09:29:49 GMT",
"via":"1.1 google",
"alt-svc":"clear",
"connection":"close"
},
"request":{
"uri":{
"protocol":"https:",
"slashes":true,
"auth":null,
"host":"api.spotify.com",
"port":443,
"hostname":"api.spotify.com",
"hash":null,
"search":null,
"query":null,
"pathname":"/v1/playlists/7fOf.....FK3z/images",
"path":"/v1/playlists/7fOf.....FK3z/images",
"href":"https://api.spotify.com/v1/playlists/7fOf.....FK3z/images"
},
"method":"PUT",
"headers":{
"Authorization":"Bearer BQDBBS2T......CZVtcz70",
"Content-Type":"image/jpeg",
"content-length":0}
}
}
https://www.base64decode.org/ which I found from this post may be useful to ensure your payload is decoding correctly to an image, you can put the base64 string in a text file and upload it to this site to see if it decodes to your desired image.
Omitting "data:image/jpeg;base64," from the start of the base64 string, if you haven't done so already, may also help.
I got the same 404 Bad Request and couldn't find out why for like 2h.. Turns out I had a \n at the end of the body text, because I imported the base64 data from a text editor that seems to add a \n automatically on save.
The body text must be without the proceeding data:image/jpeg;base64,.
I really struggle several hours to do this with axios and fetch but the base64 strings I was getting with them were not a valid one.
I then tried to use an old trick with the native XMLHttpRequest and everything works fine now. I'm downloading pictures from Spotify before uploading them to new playlist.
// Legacy code not optimized used to gather Spotify cover img in valid base64 format
function getEncodedPictureFromURL(url, uploadCallback) {
const xhRequest = new XMLHttpRequest()
xhRequest.onload = function () {
const reader = new FileReader()
reader.onloadend = function () {
uploadCallback(reader.result)
}
reader.readAsDataURL(xhRequest.response)
}
xhRequest.open('GET', url)
xhRequest.responseType = 'blob'
xhRequest.send()
}
export default {
async updatePlaylistCover(playlistId, coverUrl) {
const callback = (base64cover) => {
// Spotify does not need leading data type
const bytesNoMetadata = base64cover.split(',')[1]
// Axios object already knowing Spotify base URL and access token in the headers
request.put(`playlists/${playlistId}/images`, bytesNoMetadata, {
headers: { 'Content-Type': 'image/jpeg' }
})
}
getEncodedPictureFromURL(coverUrl, callback)
}
}
// ex
// await getEncodedPictureFromURL("PLAYLIST_ID", "https://i.scdn.co/image/ab67706c0000bebb992c70080d1de4f43fc55708")

How to upload video/image to Linkedin through API with node?

I need to upload an image/video to Linkedin through API. I am using axios and have Content-Type set to multipart/form-data and all my images/videos that need to be uploaded are stored with a url to the file. All files are stored remotely on cloudinary.
let bodyFormData = new FormData();
bodyFormData.append(
"fileupload",
request(file.url).pipe(fs.createWriteStream("video.mp4"))
);
axios
.post("https://api.linkedin.com/media/upload", bodyFormData, {
headers: {
Authorization: "Bearer " + account.accessToken,
"Content-Type": "multipart/form-data"
}
})
.then(linkedinImageResult => {
I am following this documentation here:
https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/rich-media-shares#
One of the common errors I have gotten is:
UnhandledPromiseRejectionWarning: TypeError: source.pause is not a function
If I change
request(file.url).pipe(fs.createWriteStream("video.mp4"))
to just
file.url
I get this error:
'java.io.IOException: Missing initial multi part boundary'
If I remove
"Content-Type": "multipart/form-data"
I get this error:
"Unable to parse form content"
Note:
file.url is a url to a cloudinary file, example of file.url: "https://res.cloudinary.com/dnc1t9z9o/video/upload/v1555527484/mn3tyjcpg1u4anlma2v7.mp4"
Any help is greatly appreciated :)
Please note that using Rich Media is being deprecated:
Uploading image using https://api.linkedin.com/media/upload is being
deprecated. Partners are recommended to use assets API that returns
response such as urn:li:digitalmediaAsset:C5522AQHn46pwH96hxQ to post
shares.
If you need help with the asset API, please see my answer here
I found the solution for this specific situation!
Here is my example code.
const postRichMedia = async (mediaURL, accessToken, fileName) => {
const formData = new FormData();
const key = S3Service.getSocialKeyFromUrl(mediaURL);
const file = await S3Service.downloadFileFromS3(key);
formData.append("fileupload", file.Body, fileName);
try {
const {data} = await axios.post('https://api.linkedin.com/media/upload', formData, {
headers: {
...formData.getHeaders(),
Authorization: `Bearer ${accessToken}`
},
});
const {location} = data;
return location;
} catch (err) {
console.error(err);
}
};

Node JS upload file streams over HTTP

I'm switching one of my projects from request over to something a bit more light-weight (such as got, axios, or fetch). Everything is going smoothly, however, I'm having an issue when attempting to upload a file stream (PUT and POST). It works fine with the request package, but any of the other three return a 500 from the server.
I know that a 500 generally means an issue on the server's end, but it is consistent only with the HTTP packages that I'm testing out. When I revert my code to use request, it works fine.
Here is my current Request code:
Request.put(`http://endpoint.com`, {
headers: {
Authorization: `Bearer ${account.token.access_token}`
},
formData: {
content: fs.createReadStream(localPath)
}
}, (err, response, body) => {
if (err) {
return callback(err);
}
return callback(null, body);
});
And here is one of the attempts using another package (in this case, got):
got.put(`http://endpoint.com`, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${account.token.access_token}`,
},
body: {
content: fs.createReadStream(localPath)
}
})
.then(response => {
return callback(null, response.body);
})
.catch(err => {
return callback(err);
});
Per the got documentation, I've also tried using the form-data package in conjunction with it according to its example and I still get the same issue.
The only difference between these 2 I can gather is with got I do have to manually specify the Content-Type header otherwise the endpoint does give me a proper error on that. Otherwise, I'm not sure how the 2 packages are constructing the body with the stream, but as I said, fetch and axios are also producing the exact same error as got.
If you want any of the snippets using fetch or axios I'd be happy to post them as well.
I know this question was asked a while ago, but I too am missing the simple pipe support from the request package
const request = require('request');
request
.get("https://res.cloudinary.com/demo/image/upload/sample.jpg")
.pipe(request.post("http://127.0.0.1:8000/api/upload/stream"))
// Or any readable stream
fs.createReadStream('/Users/file/path/localFile.jpeg')
.pipe(request.post("http://127.0.0.1:8000/api/upload/stream"))
and had to do some experimenting to find similar features from current libraries.
Unfortunately, I haven't worked with "got" but I hope the following 2 examples help someone else that are interested in working with the Native http/https libraries or the popular axios library
HTTP/HTTPS
Supports piping!
const http = require('http');
const https = require('https');
console.log("[i] Test pass-through: http/https");
// Note: http/https must match URL protocol
https.get(
"https://res.cloudinary.com/demo/image/upload/sample.jpg",
(imageStream) => {
console.log(" [i] Received stream");
imageStream.pipe(
http.request("http://localhost:8000/api/upload/stream/", {
method: "POST",
headers: {
"Content-Type": imageStream.headers["content-type"],
},
})
);
}
);
// Or any readable stream
fs.createReadStream('/Users/file/path/localFile.jpeg')
.pipe(
http.request("http://localhost:8000/api/upload/stream/", {
method: "POST",
headers: {
"Content-Type": imageStream.headers["content-type"],
},
})
)
Axios
Note the usage of imageStream.data and that it's being attached to data in the Axios config.
const axios = require('axios');
(async function selfInvokingFunction() {
console.log("[i] Test pass-through: axios");
const imageStream = await axios.get(
"https://res.cloudinary.com/demo/image/upload/sample.jpg",
{
responseType: "stream", // Important to ensure axios provides stream
}
);
console.log(" [i] Received stream");
const upload = await axios({
method: "post",
url: "http://127.0.0.1:8000/api/upload/stream/",
data: imageStream.data,
headers: {
"Content-Type": imageStream.headers["content-type"],
},
});
console.log("Upload response", upload.data);
})();
Looks like this was a headers issue. If I use the headers directly from FormData (i.e., headers: form.getHeaders()) and just add in my additional headers afterwards (Authorization), then this ends up working just fine.
For me just works when I added other parameters on FormData.
before
const form = new FormData();
form.append('file', fileStream);
after
const form = new FormData();
form.append('file', fileStream, 'my-whatever-file-name.mp4');
So that way I can send stream from my backend to another backend in node, waiting a file in multipart/form-data called 'file'

Resources