LinkedIn Marketing API- Invalid complete multipartUpload request - javascript

I'm trying to upload a video to the Linkedin API as per the marketing API documentation. I've done the following so far:
Registered for a multi part asset upload and received the response containing an array of unique multipart URLs to push the corresponding chunks to
Fetched the chunks from an Amazon S3 bucket where they live using the Range header
Successfully uploaded all those chunks via PUT to their corresponding URLs, and stored their ETag and HTTP Status code values.
Created the finalise POST request body as per the link above using the etags and status codes.
Unfortunately my request fails with:
'com.linkedin.vector.utils.logic.LogicLayerInvalidException: Invalid complete multipartUpload request ...(stringified payload)'
The only part of the request body I haven't added from the example given is the "metadata" field- there's nothing in the documentation to explain what this is or where it's generated from. I'm assuming that's what is missing. Can anyone point me in the right direction please?
Code for the request in question is as follows:
// Loop over chunked download URLs and upload segments of S3 file.
for(let i = 0, l = uploadDetails.partUploadRequests.length; i < l; i++) {
const item: PartUploadRequest = uploadDetails.partUploadRequests[i];
const partialParams: GetObjectRequest = { Bucket: video.dynamoData.mp4Bucket, Key: video.dynamoData.mp4Outputs[0], Range: `bytes=${item.byteRange.firstByte}-${item.byteRange.lastByte}` };
console.log(`Requesting bytes ${item.byteRange.firstByte}-${item.byteRange.lastByte}`);
const s3PartialObject = await s3Client.getObject(partialParams).promise();
const response = await axios.put(item.url, s3PartialObject.Body, {
headers: {
...item.headers
}
});
const { status, headers } = response;
responses.push({
headers: {
ETag: headers.etag
},
httpStatusCode: status
});
};
// Send all chunk responses off and confirm video upload
const finaliseVideoPayload: LinkedinFinaliseVideoPostRequest = {
completeMultipartUploadRequest: {
mediaArtifact: registerVideoRequest.value.mediaArtifact,
partUploadResponses: responses
}
};
console.log(`Fetched all parts, readying finalise request with ${finaliseVideoPayload.completeMultipartUploadRequest.partUploadResponses.length} parts.`);
const json = await axios.post('https://api.linkedin.com/v2/assets?action=completeMultiPartUpload', finaliseVideoPayload, {
headers: {
'X-RestLi-Protocol-Version': '2.0.0',
'Authorization': 'Bearer ' + channel.token,
'Host': 'api.linkedin.com'
}
});
Thanks

I guess this has been already solved. Just in case if it is not.
There is a field 'metadata' which comes in the registerApi for multipart upload. This comes in the field.
uploadMechanism["com.linkedin.digitalmedia.uploading.MultipartUpload"].metadata
Apparently, this is required by the completeMultipart api.
So data will be
completeMultipartUploadRequest: {
mediaArtifact: registerVideoRequest.value.mediaArtifact,
metadata: registerVideoRequest.value.uploadMechanism["com.linkedin.digitalmedia.uploading.MultipartUpload"].metadata,
partUploadResponses: responses
}
It took me a day to figure out that the field which has no value(empty string) is required. Maybe a bug.
Hopefully this solves the issue.

Related

Call function that returns href property when user clicks button Vue.js

So, first time posting, I usually just find the answer I need by looking through similar questions but this time I'm stumped.
First off, I'm self-taught and about on par with an entry-level developer at absolute best (for reference my highest score on CodeSignal for javascript is 725).
Here is my problem:
I'm working on an SSG eCommerce website using the Nuxt.js framework. The products are digital and so they need to be fulfilled by providing a time-limited download link when a customer makes a purchase. I have the product files stored in a private amazon s3 bucket. I also have a Netlify Serverless Function that when called with a GET request, generates and returns a pre-signed URL for the file (at the moment there is only one product but ideally it should generate pre-signed URLs based on a filename sent as a JSON event body key string since more products are planned in the near future, But I can figure that out once the whole thing is working).
The website is set up to generate dynamic routes based on the user's order number so they can view their previous orders(/pages/account/orders/_id.vue). I have placed a download button, nested in an element on this page so that each order has a button to download the files. The idea is that button press calls a function I defined in the methods object. The function makes an XMLHttpRequest to the endpoint URL of the netlify function. Netlify function returns pre-signed URL to function which returns pre-signed URL to the href property so that file can be downloaded by the user.
But no matter what I try, it fails to download the file. When the page loads it successfully calls the Netlify function and I get a response code 200 but the href property remains blank. Am I going about this the wrong way? There is clearly something I'm not understanding correctly, Any input is greatly appreciated.
Here is my code....
The download button:
<a
:download=<<MY_PRODUCT_NAME>>
:href="getmyurl()"
>
<BaseButton
v-if="order.status === 'complete'"
fit="auto"
appearance="light"
label="Download"
/>
</a>
function that button calls:
methods: {
getmyurl() {
let myurl = "";
const funcurl = <<MY_NETLIFY_FUNCTION_URL>>;
let xhr = new XMLHttpRequest();
xhr.open('GET', funcurl);
xhr.send();
xhr.onload = function() {
if (xhr.status != 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else {
myurl = xhr.response.Geturl
};
};
return myurl
},
Netlify function:
require( "dotenv" ).config();
const AWS = require('aws-sdk');
let s3 = new AWS.S3({
accessKeyId: process.env.MY_AWS_ACCESS_KEY,
secretAccessKey: process.env.MY_AWS_SECRET_KEY,
region: process.env.MY_AWS_REGION,
signatureVersion: 'v4',
});
exports.handler = function( event, context, callback ) {
var headers = {
"Access-Control-Allow-Origin" : "*",
"Access-Control-Allow-Headers": "Content-Type"
};
if ( event.httpMethod === "OPTIONS" ) {
callback(
null,
{
statusCode: 200,
headers: headers,
body: JSON.stringify( "OK" )
}
);
return;
}
try {
var resourceKey = process.env.MY_FILE_NAME
var getParams = {
Bucket: process.env.MY_S3_BUCKET,
Key: resourceKey,
Expires: ( 60 * 60 ),
ResponseCacheControl: "max-age=604800"
};
var getUrl = s3.getSignedUrl( "getObject", getParams );
var response = {
statusCode: 200,
headers: headers,
body: JSON.stringify({
getUrl: getUrl
})
};
} catch ( error ) {
console.error( error );
var response = {
statusCode: 400,
headers: headers,
body: JSON.stringify({
message: "Request could not be processed."
})
};
}
callback( null, response );
}

Unable to Make Cross Origin Resource Sharing (CORS) request to Google Web App Using JS Fetch

I made a google web app that receives post requests and posts the data to a google sheet. The code for the web app is below:
var sheetName = 'Sheet'
var scriptProp = PropertiesService.getScriptProperties()
function setup () {
var doc = SpreadsheetApp.getActiveSpreadsheet()
scriptProp.setProperty('key', doc.getId())
}
function doPost (e) {
var lock = LockService.getScriptLock()
lock.waitLock(10000)
try {
var doc = SpreadsheetApp.openById(scriptProp.getProperty('key'))
var sheet = doc.getSheetByName(sheetName)
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
// Gets the last row and then adds one
var nextRow = sheet.getLastRow() + 1
var newRow = headers.map(function(header) {
return header === 'timestamp' ? new Date() : e.parameter[header]
})
sheet.getRange(nextRow, 1, 1, newRow.length).setValues([newRow])
return ContentService
.createTextOutput(JSON.stringify({ 'result': 'success', 'row': nextRow, 'rows': newRow }))
.setMimeType(ContentService.MimeType.JSON)
}
catch (e) {
return ContentService
.createTextOutput(JSON.stringify({ 'result': 'error', 'error': e }))
.setMimeType(ContentService.MimeType.JSON)
}
finally {
lock.releaseLock()
}
}
The link to see the sheet is: https://docs.google.com/spreadsheets/d/1w8M_Jlo4YKhkbTjlXbWybiTONjWylXSFFThNPo5e9CM/edit#gid=0
I want to submit data to the sheet using a button on my static web page so I made wrote some basic JS that use fetch to submit a post request. I kept getting blocked by the cors preflight requirement since my page is static (hosted by Github Pages) and does not have a server to respond to the cors preflight. So I added heroku CORS anywhere and I still cannot get anything to post to the sheet. I have been testing by going to girling.info, my static page(still in progress) opening the debugger console and running the code. The JS fetch code:
fetch('https://cors-anywhere.herokuapp.com/https://script.google.com/macros/s/AKfycbyjt4Yg22ERgK3FS11UIPmGE1_sBLEt-kh0vRF37rAI3ujIu5DC/exec',
{
method: 'POST',
mode: 'cors',
headers: {'Origin': 'true', 'X-Requested-With': 'true', 'User-Agent': 'python-requests/2.23.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'},
body: {'Email': 'lala#la.com', 'Timestamp': 24},
}).then(response => console.log(response));
I get back a response with code=200 but no data actually posts to the sheet.
Things I have verified:
The web app works by using the command line to submit a post request with python requests
The text in my body exactly matches the columns in the sheet
I could use some help
The web app is published so that anyone can access it. Here is a screen grab of the the settings:
screen grab
I added 'cache': 'no-cache', 'redirect': 'follow' to my headers and still got the same thing. Here is a screen grab of the console. console log
By looking at the execution history for the google web api, I found that when I submit a post request using JS CORS, the web app executes but puts null in the data spots.
The problem was with caused by not having the CORS body be formatted as a JS FormData object or something that can be accessed by
var newRow = headers.map(function(header) {
return header === 'timestamp' ? new Date() : e.parameter[header]
})
When I was submitted a dictionary directly in the CORS request body, I don't think the e.parameter[header] was finding anything, so it was returning null. This explains why the google app was running but inputting null. My confirmation test was making an html page on my computer with this form
<form name="submit-to-google-sheet" method="post" target=_self>
<label for="Timestamp">Timestamp:</label> <br>
<input name="Timestamp" type="text" value="Automatically updated on submit" size="30" > <br>
<label for="0001"> How many times in the last 5 years have you seen this question in an interview:</label> <br>
<input name="0001" type="text" value=0 size="30" required> <br>
and this script
<script>
const scriptURL = 'https://script.google.com/macros/s/AKfycbyjt4Yg22ERgK3FS11UIPmGE1_sBLEt-kh0vRF37rAI3ujIu5DC/exec'
// html form
let form = document.forms['submit-to-google-sheet']
form.addEventListener('submit', e => {
e.preventDefault()
// make formdata object
let form1 = new FormData(form)
// setting timestamp
form1.set('Timestamp', new Date())
fetch(scriptURL, { method: 'POST', body: form1})
.then(response => console.log('Success!'))
.catch(error => console.error('Error!'))
reset_form = function (form_name, input_name) {
document.getElementsByName(input_name)[0].value = 'Thank your for your submission!!'
}
reset_form('submit-to-google-sheets', 'Timestamp')
reset_form('submit-to-google-sheets', '0001')
})
</script>
Thanks for all the help!
handling doGet and doPost as been a struggle I must say.
I see you have missing keys in ur Post Request from client.
Try adding
cache: 'no-cache', redirect: 'follow'.
Crossing my fingers for your code to work

Paypal Checkout client integration - Problem with returning order id promise

i have a problem integrating paypals payment gateway. I am using javascript for the client, python for the backend and the checkouts v2 api.
Creating a order on the backend works without trouble, but while waiting for my servers response the createOrder function raises a error:
unhandled_error
Object { err: "Expected an order id to be passed\nLe/</<#https://www.sandbox.paypal.com/smart/buttons?style.layout=vertical&style.color=blue&style.shape=rect&style.tagline=false&components.0=buttons&locale.country=NO&locale.lang=no&sdkMeta=eyJ1cmwiOiJodHRwczovL3d3dy5wYXlwYWwuY29tL3Nkay9qcz9jbGllbnQtaWQ9QWJmSjNNSG5oMkFIU1ZwdXl4eW5lLXBCbHdJZkNsLXpyVXc1dzFiX29TVUloZU01LXNMaDNfSWhuTnZkNUhYSW5wcXVFdm5MZG1LN0xOZ1gmZGlzYWJsZS1mdW5kaW5nPWNyZWRpdCxjYXJkIiwiYXR0cnMiOnt9fQ&clientID=AbfJ3MHnh2AHSVpuyxyne-pBlwIfCl-zrUw5w1b_oSUIheM5-sLh3_IhnNvd5HXInpquEvnLdmK7LNgX&sessionID=e2ea737589_mtc6mtu6mdi&buttonSessionID=de4bfb3626_mtc6mjm6mtk&env=sandbox&fundingEligibility=eyJwYXlwYWwiOnsiZWxpZ2libGUiOnRydWV9LCJjYXJkIjp7ImVsaWdpYmxlIjpmYWxzZSwiYnJhbmRlZCI6dHJ1ZSwidmVuZG9ycyI6eyJ2aXNhIjp7ImVsaWdpYmxlIjp0cnVlfSwibWFzdGVyY2FyZCI6eyJlbGlnaWJsZSI6dHJ1ZX0sImFtZXgiOnsiZWxpZ2libGUiOnRydWV9LCJkaXNjb3ZlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJoaXBlciI6eyJlbGlnaWJsZSI6ZmFsc2V9LCJlbG8iOnsiZWxpZ2libGUiOmZhbHNlfSwiamNiIjp7ImVsaWdpYmxlIjpmYWxzZX19…", timestamp: "1593537805136", referer: "www.sandbox.paypal.com", sessionID: "e2ea737589_mtc6mtu6mdi", env: "sandbox", buttonSessionID: "de4bfb3626_mtc6mjm6mtk" }
Error: Expected an order id to be passed
Error: Expected an order id to be passed
12V21085461823829 // ticks in a few seconds later
Console screenshot
The problem seems to be that createOrder does not wait for the promise before raising the error, or that the promise is not given in the correct way. Something like that. Anyways here is the client side code:
paypal.Buttons({
// button styling removed for clarity
createOrder: function() {
// purchase information
var data = {
'track_id': vm.selectedTrack.id,
'lease_id': vm.selectedLease.id,
}
// post req to api with lease and track ids
// create payment on server side
fetch('http://localhost:5000/api/paypal/create-purchase', {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(data),
}).then(function(res) {
return res.json();
}).then(function(data) {
console.log(data.order_id)
return data.order_id
})
}
// conatiner element to render buttons in
}).render('#paypal-button');
And the server side:
#app.route('/api/paypal/create-purchase', methods=['POST'])
def paypal_create_purchase():
# cart validation removed for clarity
# create paypal purchase
environment = SandboxEnvironment(client_id=app.config['PAYPAL_PUBLIC'], client_secret=app.config['PAYPAL_PRIVATE'])
client = PayPalHttpClient(environment)
paypal_request = OrdersCreateRequest()
paypal_request.prefer('return=representation')
paypal_request.request_body (
{
"intent": "CAPTURE",
"purchase_units": [
{
"amount": {
"currency_code": "USD",
"value": lease.price
}
}
]
}
)
try:
# Call API with your client and get a response for your call
response = client.execute(paypal_request)
order = response.result
print(order.id)
except IOError as ioe:
print (ioe)
if isinstance(ioe, HttpError):
# Something went wrong server-side
print(ioe.status_code)
# note that it is the same key as on the client
return jsonify(success=True,order_id=order.id)
I found this similar thread, but i dont consider the origin of the error to be the same as in that thread (incorrect json key on client)
Also see this relevant page in the docs which supplies this code:
createOrder: function() {
return fetch('/my-server/create-paypal-transaction', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID; // Use the same key name for order ID on the client and server
});
}
Damn, just as i was typing out the last part of the post i noticed the error. A missing return before my fetch call. Will leave this up for other people with the same mistake.

MongoDB Atlas Trigger -- Receiving a "StitchError: HTTP request failed: unexpected status code: expected=200, actual=415" error

We're trying to create a trigger in MongoDB Atlas that will automatically reduce our Azure tier of an evening to save cost. The function we've put together (probably incorrectly!) returns an error when we try to run it. The result output is:
logs:
uncaught promise rejection: StitchError: HTTP request failed: unexpected status code: expected=200, actual=415
> result:
{
"$undefined": true
}
> result (JavaScript):
EJSON.parse('{"$undefined":true}')
We've tried messing around with the headers and body, but the end result is always the same.
Here's the WIP function:
exports = function() {
const response = context.http.patch({
scheme: "https",
host: "cloud.mongodb.com",
path: "/api/atlas/v1.0/groups/abc123/clusters/fake-server",
username: "usernamehere",
password: "passwordhere",
digestAuth: true,
headers: {"Content-Type": [ "application/json" ]},
body: {"clusterType":"REPLICASET", "providerSettings":{"providerName":"AZURE", "instanceSizeName":"M10", "regionName":"regionhere" } },
encodeBodyAsJSON: true
});
};
Any help would be greatly appreciated.
It turns out that we had it right the whole time.
Executing the exact same code as above is now correctly reducing our tier. I don't know what was causing the MongoDB Atlas API to think that our headers and / or request body were incorrect, but it's perfectly happy with it today.
The only other thing I will point out is that if you want this part of the error to go away...
> result:
{
"$undefined": true
}
> result (JavaScript):
EJSON.parse('{"$undefined":true}')
...you need to change the function to async / await like so:
exports = async function() {
const response = await context.http.patch({

Error opening a Salesforce File after creating a content version using REST API

I am using typescript (angular 4) to post to the salesforce endpoint
https://yourInstance.salesforce.com/services/data/v41.0/sobjects/ContentVersion/
My http request looks like the following
Request Headers
Content-Type: multipart/form-data; boundary="1524931327799"
Authorization: Bearer <token>
Request Body
--1524931327799
Content-Disposition: form-data; name="entity_document";
Content-Type: application/json; charset=UTF-8
{
"PathOnClient" : "IMG_0400.jpg",
"Title": "IMG_0400.jpg"
}
--1524931327799
Content-Type: image/jpeg
Content-Disposition: form-data; name="VersionData"; filename="IMG_0400.jpg"
/9j/4AAQSkZJRgABAQAAAQABAAD/4QBsRXhpZgAASUkqAAgA <rest of base64 data>
--1524931327799--
After opening the image on the salesforce platform I get an error that the image may be damaged or use a file format that Preview doesn’t recognize. When I open the image using text edit there is the identical base64 data that is sent in the request. It seems the problem comes with salesforce not recognizing that the file is an image and does not decode the base64 data. All and any help is welcomed! Thank you.
I just tried it using developer workbench and request like this should work just fine. Try not to define 2 different content types in the request, rather put definition of file into the VersionData attribute of ContentVersion Object
{
"PathOnClient" : "IMG_0400.jpg",
"Title": "IMG_0400.jpg",
"VersionData" : "4AAQSkZJRgABAQAAAQABAAD/4QBsRXhpZgAASUkqAAgA"
}
I was never able to post to the /ContentVersion/ endpoint. After doing some research the simplest solution I found was to use jsforce https://jsforce.github.io/.
Solution using jsforce:
1. Import jsforce library in your index.html "https://cdnjs.cloudflare.com/ajax/libs/jsforce/1.7.0/jsforce.min.js"
2. Import jsforce at the top of your angular component
declare var jsforce:any;
Start a connection with jsforce
var conn = new jsforce.Connection({
loginUrl: "https://test.salesforce.com/",
clientId : "",
clientSecret : "",
redirectUri : ""
});
3. Login to SF and post to composite endpoint using requestPost
var username = "";
var password = "";
conn.login(username, password, function(err, userInfo) {
if (err) { return console.error(err); }
var path = '/services/data/v41.0';
return conn.requestPost( path + '/composite/', {
'allOrNone' : true,
'compositeRequest' : [
{
'method' : 'POST',
'url' : path + '/sobjects/ContentVersion/',
'referenceId' : 'newFile',
'body' : {
'Title' : fileToPost.name,
'PathOnClient' : fileToPost.name,
'VersionData' : base64FileData
}
}
]
})

Categories