Synology NAS API get free and total space - javascript

I am trying to call the API of a Synology NAS to retrieve information on free space and total space available.
My Login works, and I am getting my Token, requests with that token are shown as "successful".
However, when I try to call for the list_share method, with the additional value set to volume_status which according the documentation of the API should give me the desired data, all I get is:
{
"data": {
"offset": 0,
"shares": [],
"total": 0
},
"success": true
}
The request I am making is:
https://NAME:5001/webapi/entry.cgi?api=SYNO.FileStation.List&version=2&method=list_share&additional=volume_status&_sid=TOKEN
Am I making the wrong request? Do I need to change the Method from list_share to list?
Am I passing the additional value wrong?
The Documentation I am using can be found when googling for 'Synology NAS rest API'. I inserted the direct download link for the PDF here.

According to the linked documentation, the additional parameter needs additional brackets and quotes like this:
https://NAME:5001/webapi/entry.cgi?api=SYNO.FileStation.List&version=2&method=list_share&additional=["volume_status"]&_sid=TOKEN
Depending on the library you use you might need to encode the brackets and the quotes, but most libraries should do that for you :)
To come to that conclusion, this is what i did:
In the linked documentation i found the section about list_share and the provided example:
GET /webapi/entry.cgi?api=SYNO.FileStation.List&version=2&method=list_share& additional=%5B%22real_path%22%2C%22owner%2Ctime%22%5D
This is completely unreadable, since the url is already encoded, but opening the browser console and running
decodeURIComponent("/webapi/entry.cgi?api=SYNO.FileStation.List&version=2&method=list_share&additional=%5B%22real_path%22%2C%22owner%2Ctime%22%5D")
leads me to a readable url:
/webapi/entry.cgi?api=SYNO.FileStation.List&version=2&method=list_share&additional=["real_path","owner,time"]
This is an example, how to login, print out the data and logout again:
const http = require("http");
// Helper function to get JSON from an endpoint
function getJson(url) {
return new Promise((resolve, reject) => {
http.get(url, (res) => {
res.setEncoding("utf8");
let rawData = "";
res.on("data", (chunk) => {
rawData += chunk;
});
res.on("end", () => {
try {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
} catch (e) {
reject(e);
}
});
});
});
}
(async () => {
const host = "synology:5000";
const user = "userHere";
const password = "passwordHere";
// login
const {
data: { sid },
} = await getJson(
`http://${host}/webapi/query.cgi?api=SYNO.API.Auth&method=login&version=3&account=${user}&passwd=${password}&session=test&format=sid`
);
const url = `http://${host}/webapi/query.cgi?api=SYNO.FileStation.List&method=list_share&version=2&_sid=${sid}&additional=["volume_status"]`;
const { data } = await getJson(url);
data.shares.forEach((share) => {
console.log(share.name, "-", share.path);
console.log(share.additional.volume_status);
});
//logout
await getJson(
`http://${host}/webapi/query.cgi?api=SYNO.API.Auth&method=logout&version=3&session=test`
);
})();

Related

How to do A/B testing with Cloudflare workers

I'm looking for an example of what these two lines of code look like in a functioning A/B testing worker. From https://developers.cloudflare.com/workers/examples/ab-testing
const TEST_RESPONSE = new Response("Test group") // e.g. await fetch("/test/sompath", request)
const CONTROL_RESPONSE = new Response("Control group") // e.g. await fetch("/control/sompath", request)
I used the examples, subsituting the paths I’m using, and got a syntax error saying await can only be used in async. So I changed the function to async function handleRequest(request) and got a 500 error.
What should these two lines look like for the code to work?
Working example
document.getElementById("myBtn").addEventListener("click", myFun);
async function myFun() {
const isTest = Math.random() > 0.5 //here you place your condition
const res = isTest ? await fetch1() : await fetch1() //here you should have different fetches for A and B
console.log(res)
document.getElementById("demo").innerHTML = res.message;
}
async function fetch1() {
//I took the link from some other SO answer as I was running into CORS problems with e.g. google.com
return fetch("https://currency-converter5.p.rapidapi.com/currency/list?format=json", {
"method": "GET",
"headers": {
"x-rapidapi-host": "currency-converter5.p.rapidapi.com",
"x-rapidapi-key": "**redacted**"
}
})
.then(response => response.json())
}
<button id="myBtn">button</button>
<div id="demo">demo</div>
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script type="text/babel">
</script>
Okay, I've worked on this a bit and this worker below seems to fulfill your requirements:
async function handleRequest(request) {
const NAME = "experiment-0";
try {
// The Responses below are placeholders. You can set up a custom path for each test (e.g. /control/somepath ).
const TEST_RESPONSE = await fetch(
"https://httpbin.org/image/jpeg",
request
);
const CONTROL_RESPONSE = await fetch(
"https://httpbin.org/image/png",
request
);
// Determine which group this requester is in.
const cookie = request.headers.get("cookie");
if (cookie && cookie.includes(`${NAME}=control`)) {
return CONTROL_RESPONSE;
} else if (cookie && cookie.includes(`${NAME}=test`)) {
return TEST_RESPONSE;
} else {
// If there is no cookie, this is a new client. Choose a group and set the cookie.
const group = Math.random() < 0.5 ? "test" : "control"; // 50/50 split
const response = group === "control" ? CONTROL_RESPONSE : TEST_RESPONSE;
return new Response(response.body, {
status: response.status,
headers: {
...response.headers,
"Set-Cookie": `${NAME}=${group}; path=/`,
},
});
}
} catch (e) {
var response = [`internal server error: ${e.message}`];
if (e.stack) {
response.push(e.stack);
}
return new Response(response.join("\n"), {
status: 500,
headers: {
"Content-type": "text/plain",
},
});
}
}
addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
There are some issues with the snippet once you uncomment the fetch() parts:
Yes, the function needs to be async in order to use await keyword, so I added that.
Additionally, I think the 500 error you were seeing was also due to a bug in the snippet: You'll notice I form a new Response instead of modifying the one that was chosen from the ternary expression. This is because responses are immutable in the workers runtime, so adding headers to an already instantiated response will result in an error. Therefore, you can just create an entirely new Response with all the bits from the original one and it seems to work.
Also, in order to gain insight into errors in a worker, always a good idea to add the try/catch and render those errors in the worker response.
To get yours working on this, just replace the httpbin.org urls with whatever you need to A/B test.

Electron BrowserWindow cannot get response when debugger is attached

I'm writing an Electron app which creates a BrowserWindow. I want to capture a few requests sent to a server, I also want responses for there requests. Using Electron WebRequest api I can't get responses, so searched the web and found that I can attach a debugger programatically.
I attach debugger using the below code and I get almost all responses correctly. But for one bigger request I can't get the response. I get an error
Error: No resource with given identifier found
If I launch DevTools and navigate to that request I also can't get the response: Failed to load response data. If I comment out below code the response in DevTools show correctly.
Note that this happens only for one specific request which returns about 1MB response. For all other requests I can get the response using getResponseData().
const dbg = win.webContents.debugger
var getResponseData = async (reqId) => {
const res = await dbg.sendCommand("Network.getResponseBody", {requestId: reqId});
return res.body
}
try {
dbg.attach('1.3')
dbg.sendCommand('Network.enable')
} catch (err) {
console.log('Debugger attach failed : ', err)
}
dbg.on('detach', async (event, reason) => {
console.log('Debugger detached due to : ', reason)
})
dbg.on('message', (e, m, p) => {
if (m === 'Network.requestWillBeSent') {
if (p.request.url === someURL) {
const j = JSON.parse(p.request.postData)
console.log("req " + p.requestId)
global.webReqs[p.requestId] = { reqData: j}
}
} else if (m === 'Network.loadingFinished') {
if (p.requestId in global.webReqs) {
console.log("res " + p.requestId)
getResponseData(p.requestId).then(res => {
console.log(res.slice(0,60))
}).catch(err => {
console.error(err)
})
}
}
});
Short update
The event stack for this particular request is as follows, where 13548.212 is simply requestId
Network.requestWillBeSentExtraInfo 13548.212
Network.requestWillBeSent 13548.212
Network.responseReceivedExtraInfo 13548.212
Network.responseReceived 13548.212
Network.dataReceived 13548.212 [repeated 135 times]
...
Network.loadingFinished 13548.212
Looks that I found a solution. It's rather a workaround, but it works. I didn't use Network.getResponseBody. I used Fetch(https://chromedevtools.github.io/devtools-protocol/tot/Fetch).
To use that one need to subscribe for Responses matching a pattern. Then you can react on Fetch.requestPaused events. During that you have direct access to a request and indirect to a response. To get the response call Fetch.getResponseBody with proper requestId. Also remember to send Fetch.continueRequest as
The request is paused until the client responds with one of continueRequest, failRequest or fulfillRequest
https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-requestPaused
dbg.sendCommand('Fetch.enable', {
patterns: [
{ urlPattern: interestingURLpattern, requestStage: "Response" }
]})
var getResponseJson = async (requestId) => {
const res = await dbg.sendCommand("Fetch.getResponseBody", {requestId: requestId})
return JSON.parse(res.base64Encoded ? Buffer.from(res.body, 'base64').toString() : res.body)
}
dbg.on('message', (e, m, p) => {
if(m === 'Fetch.requestPaused') {
var reqJson = JSON.parse(p.request.postData)
var resJson = await getResponseJson(p.requestId)
...
await dbg.sendCommand("Fetch.continueRequest", {requestId: p.requestId})
}
});

Why the data doesn't want to be appear in my .json file using nodeJS?

1.) I have a function that successfully generates random words in accordance with the user input (if user input is 10 than 10 random words will be displayed on the website). A have a json server were I'm getting the words using GET request fetch api as you can see below:
function getRandomWords() {
let words = getServerData('http://localhost:3000/words').then(data => {
const wordsToMemorize = document.querySelector('#words-to-memorize');
document.querySelector("#wordsInput").addEventListener("click", function() {
let temp = wordsToMemorize.value;
generatedArrayELements.innerHTML = "";
for(let i = 0; i < temp; i++) {
let rander = Math.floor(Math.random() * 3000);
generatedArrayELements.innerHTML += data[rander] + "</br>";
}})
});
}
2.) I want to transfer the generated words to my .json file in to the randomwords array but it doesn't want to work. And using DOM down there also doesnt make sense that I just realised:
function putServerData() {
let data = getRandomWords();
let fetchOptions = {
method: "POST",
mode: "cors",
cache: "no-cache",
headers: {
'Content-Type': 'application/json'
},
credentials: "same-origin",
body: JSON.stringify(data)
};
return fetch("http://localhost:3000/randomwords", fetchOptions)
.then(resp => resp.json())
.then(json => console.log(json));
}
document.querySelector("#wordsInput").addEventListener("click", function() {
putServerData();
})
3.) I feel like being very close to my goal because the random word generator works and I can also POST data in to the .json file if I set manually the value to the let data variable, like let data = ["test"]. What I dont know is how to send the generated random words in to my .json file.
4.) My json file:
{
"randomwords": [],
"words": [
"a",
"abandon",
"ability",
"able",
"abortion",
"about",
"above",
"abroad",
"absence",
"absolute",
"absolutely",
"absorb",
"abuse",
"academic",
"accept",
"access"...(remaining words...)
}]
4.) I tried to follow the documentation. Maybe I need to use some timeout because the random words first need to be generated before I use POST request on them. I've been struggling with this for almost a week and cant find solution, I read almost every relating post in SO. Every help would be greatly appreciated.
return a promise in my getRandomWords() with some timeout can be a working solution?
You can't add date to json file by making a fetch post call, however what you can do is write the data to your json file, you can do that by using the node fs module.
first make a route handler
const fs = require('fs')
router.post("/savedata", (req, res) => {
const jsondata = req.body
fs.writeFileSync('./path/to/your/json/data', jsondata, err => {
if (err) {
res.send("error saving file")
} else {
res.send("data saved successfully")
}
})
})
and then use fetch to make a call to your passing the data in the body

Issue to access data on json response from https request from nodejs

I have coded an HTTPS function using the native https module from NodeJS to get some JSON from an API.
When I'm doing a GET request I'm getting the JSON fine, but when I have to make a PUT request I got the JSON but I can't convert that into an object.
answer from GET:
{
items: [
{ dsname: 'A09999' },
{ dsname: 'A09999.ALLFILES.TEMP' },
],
returnedRows: 16,
JSONversion: 1
}
answer from PUT:
{
'cmd-response-key': 'C8124546',
'cmd-response-url':'https://myhost/zosmf/restconsoles/consoles/ibmusecn/solmsgs/C8124546',
'cmd-response-uri': '/zosmf/restconsoles/consoles/ibmusecn/solmsgs/C8124546',
'cmd-response':
' IEE457I 16.32.55 UNIT STATUS 464\r UNIT TYPE STATUS VOLSER' }
My code:
const makeRequest = options =>
new Promise(function(resolve, reject) {
let results = https.request(options, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', data => {
body += data;
});
res.on('end', () => {
body = JSON.parse(body);
console.log('body :', body);
resolve(body);
});
});
if (options.body) {
// results.write(options.body);
results.write(JSON.stringify(options.body),['Transfer-Encoding', 'chunked']);
}
results.on('error', e => {
reject(e);
});
results.end();
});
Well what you are getting back from the post is not a valid json object. This bit right here:
'cmd-response':
' IEE457I 16.32.55 UNIT STATUS 464\r UNIT TYPE STATUS VOLSER'' }
Notice how at the end, VOLSER is followed by to ''. This ends the string starting the value and opens a new string. This is invalid JSON.
Also, I'd recommend using a library like axios or request from npm. Well written, widely used, and not as low level as using the https module directly.

How do I pass a react DOM state's information to backend(NodeJS) when a button is invoked?

I'm using his logic on the frontend, but I'm having some trouble actually receiving that data on the backend. I'm using the Sails.js framework. Any suggestions?
handleSubmit = () => {
// Gathering together the data you want to send to API
const payload = {
subject: this.state.subject,
message: this.state.message,
};
this.handleAjaxRequest(payload);
};
// Method to send data to the backend
// Making the req -I'm using Axios here.
handleAjaxRequest = (payload) => {
let request = axios({
method: 'post',
url: '/api/',
data: payload,
headers: 'Content-Type: application/json'
});
// Do stuff with the response from your backend.
request.then(response => {
console.debug(response.data);
})
.catch(error => {
console.error(error);
})
};
I used to do this using Express and didn't have these problems.
Any help, method, a suggestion is more than welcome :)
Please forgive my ignorance, I'm just here to learn.
Okay, so the first thing I had to do is generate a new restful API using the command sails generate api data. In the package.json file I set up a proxy that includes the backends endpoint, like this "proxy": "http://localhost:1337" - I mean, you don't need to do this, but if you don't then you have to include this URL part on every request. Because it doesn't change, it's pretty convenient to do so.
On the frontend, I made a function sendData() that takes the necessary data from my previous component (depending on what the user selected) and send that data using axios to the backend -->
sendData = () => {
const { relYear } = this.props.history.location.state.dev;
const { relMonth } = this.props.history.location.state.dev;
const selectedMonth = moment().month(relMonth).format("MM");
const finalSelect = parseInt(relYear + selectedMonth, 10);
axios.post('/data', { 'selectedDate' : finalSelect })
.then(res => console.log('Data send'))
.catch(err => console.error(err));
}
On the backend I fetched the data, did the calculations and send back the result to the frontend of my app. -->
getApiData = () => {
let apiData = [];
axios.get('/data')
.then(res => {
let first = Object.values(res.data.pop()).shift(); // Getting the relevant 'selectedDate'
apiData.push(first);
}).catch(err => console.error(err));
return apiData;
}

Categories