How to make a shipping request to chronopost using soap and nodejs - node.js

I've had trouble getting to make a simple request with node-soap and chronopost (shipping platform) soap api.
the first thing I did was to follow the basic node-soap example but it just fails miserably without any real USEFUL error from chronopost.
Here's what I have:
const soap = require('soap')
const client = await soap.createClientAsync(
'https://ws.chronopost.fr/shipping-cxf/ShippingServiceWS?wsdl'
)
client.shippingV6(...somedata, (err, result) => {
if (err) {
return handleErr(); // it always fails
}
handleResult();
})

After multiple attempt it seems like chronopost api uses special root attributes (who knows why) and you need to craft the options on node-soap that actually fit their needs (yay..)
Here's what works for me
const createClientShippingServiceWS = async () => {
const wsdlOptions = {
envelopeKey: 'soapenv',
overrideRootElement: {
namespace: 'cxf',
xmlnsAttributes: [
{
name: 'xmlns:cxf',
value: 'http://cxf.shipping.soap.chronopost.fr/'
}
]
}
}
return await soap.createClientAsync(
'https://ws.chronopost.fr/shipping-cxf/ShippingServiceWS?wsdl',
wsdlOptions
)
}
Also what's the point of getting the wsdl if node-soap can't event figure how to make the response??
Thanks chronopost for being stuck in 2008

Related

strong-soap return wsdl different from soap

I am having a bit of an issue, and I hope someone may be able to help. Our company is switching from npm soap library to strong-soap for internal reasons. We are interfacing with an old server (soap 1.1) that we prefer not to modify. We can set up the client perfectly with the soap library; however, with strong-soap, it appears we get some questionable wsdl returned. If we call describe on the soap client we get all the proper ins and outs. However, if we do a describe on the strong-soap we get the return function with a slew of xml. I think the wsdl description coming back for the strong-soap must not be proper. I am wondering if strong-soap supports version 1.1.
If we call a method in the implementation of strong-soap, we get an error message “missing required input parameter”, which makes sense because the data returned from describe lists a giagantic xml description for the parameters. This is the reason I do not think the strong-soap supports an earlier version of soap.
For the soap libary
`
const authorization = `Basic ${base64.encode(`${userId}:${password}`)}`
const wsdlOptions = createWsdlOptions(authorization)
client = await soap.createClientAsync(url, wsdlOptions)
client.addHttpHeader('Authorization', authorization)
client.setEndpoint(url)
client.on('soapError', (e, _eid) => {
error(`SOAP client error: ${e.message}`)
})
`
For the strong-soap libary
`
return new Promise((resolve, reject) => {
const authorization = `Basic ${base64.encode(`${userId}:${password}`)}`
const wsdlOptions = createWsdlOptions(authorization)
soap.createClient(url, wsdlOptions, (err, c) => {
if (err) {
reject(err)
}
that.client = c
that.client.setSecurity(new soap.BasicAuthSecurity(userId, password))
that.client.setEndpoint(url)
that.client.on('soapError', (e, _eid) => {
error(`SOAP client error: ${e.message}`)
})
resolve(c)
})
})
`
If I try a describe on the client
`
const description = client.describe()
console.log(JSON.stringify(description.CClmStatusWF.BasicHttpBinding_IClmStatusWF.readMyData)))
`
On the soap client i get
{"input":{"p_sXMLParam":"xs:string"},"output":{"readMyDataResult":"xs:string"}}
However on the srong-soap I get a huge of xml file

Node.js Soap method params parsed as string instead of related object

I'm using node-soap to consume a WebService. I've used this library and consumed this service many times before, but this time I'm having the following situation.
I create the client using the WSDL that contains the methods, inputs etc:
const soap = require("soap");
const url = "https://servintp.latinoseguros.com.mx:8071/wsCotizadorAutos/cotizador/CotizadorLatino.svc?wsdl";
soap.createClient(url, {}, (error, client) => {
if (error) {
callback(error);
} else {
const describe = client.describe()["CotizadorLatino"]["BasicHttpBinding_ICotizadorLatino"];
console.log(describe["ObtenerMarcas"])
}
});
The request is successful and the client is created, but when I describe the method "ObtenerMarcas" I receive the following object:
{
input: { datos: 'q17:DatosRequeridos' },
output: { ObtenerMarcasResult: 'q18:ListMarca' }
}
Whose only input param shows as only a String, while it should be an object whith different objects and attributes.
When using Soap Client and inputing the exact same WSDL endpoint, the method is described as it should, with all the children and attributes that are expected to be passed:
Soap Client Screenshot
Which makes me believe that this has something to do with the client configuration or probably something very basic that I'm not taking into account.
Can anybody point me on what I'm doing wrong?
Thanks!

How to request data with token authorization in custom functions in Office JS Excel-Add-In?

What I have:
I adapted this example from Microsoft docs.
// functions.js
/**
* Get data
* #customfunction
* #returns {string[][]}
*/
async function getData() {
try {
const url = "https://api.example.com/some/objects/";
const token = await OfficeRuntime.storage.getItem("Token");
const authString = `Token ${token.toString()}`;
const response = await fetch(url, {
headers: { Authorization: authString }
});
if (!response.ok) {
throw new Error(response.statusText);
}
const jsonResponse = await response.json();
return jsonResponse.map(obj => {return [obj.id.toString(), obj.name]};
} catch (error) {
return [["ERROR", error.message]];
}
}
Added api.example.com to <AppDomains>
Item "Token" is present in OfficeRuntime.storage
Same API call with Postman works fine
The Add-In is not served from localhost (because of CORS reasons etc.)
What I get:
Because it is not developed locally it is very hard to debug ui-less custom functions. Therefore the only visible error I get so far, is the one I receive and return to Excel in the catch-block. It is an unhelpful error message: Network request failed
Can anyone help me with any suggestions?
Reason why it did not work was a known issue that custom functions run in a separate JavaScript runtime as described here. This runtime allows only CORS-safelisted request header because it lacks of CORS-Preflight -> therefore the Network request failed error with the Authorization header.
How I solved it:
Configure the Excel-Add-in to use a shared JavaScript runtime as described here.
Add <script src="functions.js"></script> just before the <\head> element in taskpane.html as described here.
Build the project npm run build
Clear the cache as described in the first two steps here.
Run Excel and load your Add-in.

How can one upload an image to a KeystoneJS GraphQL endpoint?

I'm using TinyMCE in a custom field for the KeystoneJS AdminUI, which is a React app. I'd like to upload images from the React front to the KeystoneJS GraphQL back. I can upload the images using a REST endpoint I added to the Keystone server -- passing TinyMCE an images_upload_handler callback -- but I'd like to take advantage of Keystone's already-built GraphQL endpoint for an Image list/type I've created.
I first tried to use the approach detailed in this article, using axios to upload the image
const getGQL = (theFile) => {
const query = gql`
mutation upload($file: Upload!) {
createImage(file: $file) {
id
file {
path
filename
}
}
}
`;
// The operation contains the mutation itself as "query"
// and the variables that are associated with the arguments
// The file variable is null because we can only pass text
// in operation variables
const operation = {
query,
variables: {
file: null
}
};
// This map is used to associate the file saved in the body
// of the request under "0" with the operation variable "variables.file"
const map = {
'0': ['variables.file']
};
// This is the body of the request
// the FormData constructor builds a multipart/form-data request body
// Here we add the operation, map, and file to upload
const body = new FormData();
body.append('operations', JSON.stringify(operation));
body.append('map', JSON.stringify(map));
body.append('0', theFile);
// Create the options of our POST request
const opts = {
method: 'post',
url: 'http://localhost:4545/admin/api',
body
};
// #ts-ignore
return axios(opts);
};
but I'm not sure what to pass as theFile -- TinyMCE's images_upload_handler, from which I need to call the image upload, accepts a blobInfo object which contains functions to give me
The file name doesn't work, neither does the blob -- both give me server errors 500 -- the error message isn't more specific.
I would prefer to use a GraphQL client to upload the image -- another SO article suggests using apollo-upload-client. However, I'm operating within the KeystoneJS environment, and Apollo-upload-client says
Apollo Client can only have 1 “terminating” Apollo Link that sends the
GraphQL requests; if one such as apollo-link-http is already setup,
remove it.
I believe Keystone has already set up Apollo-link-http (it comes up multiple times on search), so I don't think I can use Apollo-upload-client.
The UploadLink is just a drop-in replacement for HttpLink. There's no reason you shouldn't be able to use it. There's a demo KeystoneJS app here that shows the Apollo Client configuration, including using createUploadLink.
Actual usage of the mutation with the Upload scalar is shown here.
Looking at the source code, you should be able to use a custom image handler and call blob on the provided blobInfo object. Something like this:
tinymce.init({
images_upload_handler: async function (blobInfo, success, failure) {
const image = blobInfo.blob()
try {
await apolloClient.mutate(
gql` mutation($image: Upload!) { ... } `,
{
variables: { image }
}
)
success()
} catch (e) {
failure(e)
}
}
})
I used to have the same problem and solved it with Apollo upload link. Now when the app got into the production phase I realized that Apollo client took 1/3rd of the gzipped built files and I created minimal graphql client just for keystone use with automatic image upload. The package is available in npm: https://www.npmjs.com/package/#sylchi/keystone-graphql-client
Usage example that will upload github logo to user profile if there is an user with avatar field set as file:
import { mutate } from '#sylchi/keystone-graphql-client'
const getFile = () => fetch('https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png',
{
mode: "cors",
cache: "no-cache"
})
.then(response => response.blob())
.then(blob => {
return new File([blob], "file.png", { type: "image/png" })
});
getFile().then(file => {
const options = {
mutation: `
mutation($id: ID!, $data: UserUpdateInput!){
updateUser(id: $id, data: $data){
id
}
}
`,
variables: {
id: "5f5a7f712a64d9db72b30602", //replace with user id
data: {
avatar: file
}
}
}
mutate(options).then(result => console.log(result));
});
The whole package is just 50loc with 1 dependency :)
The easies way for me was to use graphql-request. The advantage is that you don't need to set manually any header prop and it uses the variables you need from the images_upload_handler as de docs describe.
I did it this way:
const { request, gql} = require('graphql-request')
const query = gql`
mutation IMAGE ($file: Upload!) {
createImage (data:
file: $file,
}) {
id
file {
publicUrl
}
}
}
`
images_upload_handler = (blobInfo, success) => {
// ^ ^ varibles you get from tinymce
const variables = {
file: blobInfo.blob()
}
request(GRAPHQL_API_URL, query, variables)
.then( data => {
console.log(data)
success(data.createImage.fileRemote.publicUrl)
})
}
For Keystone 5 editorConfig would stripe out functions, so I clone the field and set the function in the views/Field.js file.
Good luck ( ^_^)/*

Create REST full service using Node js

I would like to build a node application using REST, need to read data from readymade api and store it in class for temp, and then save it to MySQL. Can anyone have any idea about this?
This is a very simple job.
Let's consider Typescript, but you can achieve the same result with JavaScript. I'll be using node-fetch as an example of the rest API library. Do note that the code might not be syntactically correct.
First: Create interfaces/classes that reflect the data you will receive from the REST API
interface Food {
id: number,
name: string,
...
}
Second:
Create a Repository
Create a class Repository which you will use to communicate with the rest API
class Repository {
async function getFoods(...args): List<Food> {
let foods = await fetch({url: "url"});
return foods;
}
async function addFood(food: Food): Response {
let response = await fetch({
url: "url-to-add-food",
method: "post",
data: JSON.stringify(food)
});
}
}
Third:
Use the repository to fetch the data and use conventional methods to save it to a MySQL database
let foods = await repository.getFoods();
foods.forEach(food => {
connection.query('INSERT INTO foods SET ?', food,
function (err, resp) {
if (err) throw err;
}
);
});

Resources