When trying to log into textnow through an API using the correct username and password, the following error occurs:
UnhandledPromiseRejectionWarning: Error: 401 Unauthorized
at _response.transport.request.then (E:\nodejs\node_modules\snekfetch\src\index.js:193:21)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:19732) UnhandledPromiseRejectionWarning: Unhandled promise rejection.
This error originated either by throwing inside of an async function without
a catch block, or by rejecting a promise which was not handled with .catch().
(rejection id: 2)
Here's a look at the code from the API that I'm using:
module.exports.textnowLogin = (email, password) => {
return new Promise((resolve, reject) => {
let json = { "password": password, "username": email };
let queryEndpoint = "sessions?client_type=TN_ANDROID";
let signature = md5(`${tnSignatureKey}POST${queryEndpoint}${JSON.stringify(json)}`);
snekfetch.post(`https://api.textnow.me/api2.0/${queryEndpoint}&signature=${signature}`)
.set("Content-Type", "application/json")
.send(json)
.then((result) => {
return resolve(result.body);
}).catch(reject);
});
};
Here's a look at how I use this method in my js file:
const textNow = require('textnow-api');
textNow.login(username, password).then(client => {
console.log(`Logged in as ${client.username}`);
});`
This definitely has to be a server side issue, no? Something must be going wrong on Textnow's end. What can I do to circumvent this?
EDIT: const snekfetch = require("snekfetch"),
md5 = require("md5"),
tnSignatureKey = "f8ab2ceca9163724b6d126aea9620339";
Where did this key originate from? Perhaps if a new one was generated then the authorization error would be solved?
As a side note, another potential issue could be the client_type being set to ANDROID, and I am trying to use an iOS account to login. However, whenever I try using an Android account to log in instead, I get a 400 Bad Request, like Textnow does not recognize the account's credentials.
There may be other errors in the code, but the first mistake is the creation of an extra promise. Here's a version of the code that solves that and should either work or be simpler to debug...
module.exports.textnowLogin = (email, password) => {
let json = { "password": password, "username": email };
let queryEndpoint = "sessions?client_type=TN_ANDROID";
let signature = md5(`${tnSignatureKey}POST${queryEndpoint}${JSON.stringify(json)}`);
let url = `https://api.textnow.me/api2.0/${queryEndpoint}&signature=${signature}`;
return snekfetch.post(url).set("Content-Type", "application/json").send(json).then(result => {
return result.body;
});
};
Related
I want to use web3.js to show revert reason to user, for example in the case of user trying to mint erc721 token that has already been minted. I am using try catch block and see the error message but I want to isolate the error message to show the user a meaningful reason. Thanks in advance.
The previous answer by #Petr Hejda didn't work for me, and neither did his suggestion in response to #Chakshu Jain's problem in the comments.
Instead, I removed some characters—from the start and the end, with slice()—that were causing the error when parsing the JSON, so I could handle the error message and get the error message.
if (err) {
var errorMessageInJson = JSON.parse(
err.message.slice(58, err.message.length - 2)
);
var errorMessageToShow = errorMessageInJson.data.data[Object.keys(errorMessageInJson.data.data)[0]].reason;
alert(errorMessageToShow);
return;
}
It's returned in the JS error object as data.<txHash>.reason.
This is a faulty Solidity code
pragma solidity ^0.8.0;
contract Test {
function foo() public {
revert('This is error message');
}
}
So a transaction calling the foo() function should revert with the message This is error message.
try {
await myContract.methods.foo().send();
} catch (e) {
const data = e.data;
const txHash = Object.keys(data)[0]; // TODO improve
const reason = data[txHash].reason;
console.log(reason); // prints "This is error message"
}
After trying out every solution on stackoverflow, random blogs, and even the officially documented "web3.eth.handleRevert = true", none is working for me.
I finally figured out after 25 failed attempts:
try {
await obj.methods.do_something().call({
gasLimit: String(GAS_LIMIT),
to: CONTRACT_ADDRESS,
from: wallet,
value: String(PRICE),
})
}
catch (err) {
const endIndex = err.message.search('{')
if (endIndex >= 0) {
throw err.message.substring(0, endIndex)
}
}
try {
const res = await obj.methods.do_something().send({
gasLimit: String(GAS_LIMIT),
to: CONTRACT_ADDRESS,
from: wallet,
value: String(PRICE),
})
return res.events.Transfer.returnValues.tokenId
}
catch (err) {
console.error(err)
throw err
}
The idea is to use call first. This method doesn't interact with your Metamask, but merely checks if your input arguments go through the contract method. If it can't go through, it will throw exception in the first catch block. If it does go through, we are safe to do use send. This method interacts with your Metamask for real. We have a second catch block in case there are wallet connection or gas fee issues
It is really perplexing why Solidity/Web3 don't have an easy way to extract the require/revert reason from the error object.
For me, the "require" reason is there in the message property of the error object, but it is surrounded by lot of other words which I don't need.
An example error message:
[ethjs-query] while formatting outputs from RPC '{"value":{"code":-32603,"data":{"message":"VM Exception while processing transaction: revert Voting is closed","code":-32000,"data":{"0xf901429f12096d3b5c23a80e56fd2230fa37411bb1f8d3cdbd5c8f91c2670771":{"error":"revert","program_counter":43,"return":"0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000165f5f5f566f74696e6720697320636c6f7365645f5f5f00000000000000000000","reason":"Voting is closed"},"stack":"RuntimeError: VM Exception while processing transaction: revert Voting is closed \n at Function.RuntimeError.fromResults (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/utils/runtimeerror.js:94:13)\n at BlockchainDouble.processBlock (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/blockchain_double.js:627:24)\n at runMicrotasks (<anonymous>)\n at processTicksAndRejections (internal/process/task_queues.js:93:5)","name":"RuntimeError"}}}}'
You can see the reason Voting is closed stuck in between. Not that user-friendly to read.
I've seen answers that use regex to extract the error reason.
For those like me, who are not a big fan of the regex way, here is my approach.
In your solidity contract, wrap the require reason with a unique delimiter of sorts. In my case, it is "___" (3 underscores).
contract MyContract{
...
...
function vote(address _addr) public payable{
require(votingOpen, "___Voting closed___");
...
}
...
...
}
Declare a helper function to extract the error using JavaScript string utilities. Here's where your delimiter coes in handy.
export const extractErrorCode = (str) => {
const delimiter = '___'; //Replace it with the delimiter you used in the Solidity Contract.
const firstOccurence = str.indexOf(delimiter);
if(firstOccurence == -1) {
return "An error occured";
}
const secondOccurence = str.indexOf(delimiter, firstOccurence + 1);
if(secondOccurence == -1) {
return "An error occured";
}
//Okay so far
return str.substring(firstOccurence + delimiter.length, secondOccurence);
}
Use this function where you catch the error in your frontend
const vote = async (_addr) => {
setLoading(true);
try {
await contest.methods.vote(_addr).send({
from: accounts[0],
})
}
catch (e) {
console.log('Voting failed with error object => ', e)
console.log('Voting failed with the error => ', extractErrorCode(e.message))
}
setLoading(false);
}
Until Solidity & Web3.js (and ether.js) come out with a clean way to parse errors, we are stuck with workarounds like this.
I prefer this workaround over others because I am not that great with regex, and additionally, this one does not depend on a fixed starting position to extract the error code.
Did you try something like this?
error.toString()
It works for me just to show the revert error in the Smart Contract, and return it as a string message.
try {
//Do something
} catch (error) {
res.send({
'status': false,
'result': error.toString()
});
}
I am new to js/node/express, and I have been working on this application where I have the following code to handle user registration:
const Account = require('../models/Account.js')
module.exports = {
// TODO: Check why Postman hangs on POST request for this
async register (req, res, next) {
if (req.body.email && req.body.password) {
var newAccount = new Account()
newAccount.email = req.body.email
newAccount.password = newAccount.generateHash(req.body.password)
const account = Account.create({email: newAccount.email, password: newAccount.password}, function (err, res) {
if (err) {
console.log('could not insert. Check error.')
// CANT CALL res.status(400).send({ error: 'email already exists'})
res.status(500)
return next(err)
}
res.status(400).send({
error: 'exists'
})
})
console.log(`inserted account ${newAccount.email}`)
res.send(account.toJSON())
}
}
}
I read this post about how to properly send JSON data back in order to build a proper REST API but ran into some issues.
When I do the call to res.status(400) I get an error that res.status is not a function. Is that because res is not available in that if statement? If it isn't how then, do I properly send a 400 (or any error status) in a case like this?
I want to be able to send an error message if the saving into my mongo db fails, or send back the created user if the insertion was successful.
If there is anything out there that I can read Id love to read some of that as well.
When I do the call to res.status(400) I get an error that res.status is not a function.
That's because you are defining res as an argument to the callback in this line:
const account = Account.create({email: newAccount.email, password: newAccount.password},
function (err, res) {
And that res hides the higher scoped res. The solution is to not have a name conflict. Change the name of this res to be accountRes or something like that. You have to be aware of name conflicts in declared argument names when nesting inline functions.
It also looks like:
res.send(account.toJSON())
is in the wrong place. You will send that BEFORE Account.create() finishes its asynchronous work. That probably needs to be inside the callback.
Speaking of proper error handling, if this if (req.body.email && req.body.password) test fails, then you don't send any response at all. You need to always send some sort of response to an http request. I'd suggest adding an else to that if and send an appropriate response.
Trying my first angular exercise. Receiving undefined value on 1st time from http post, but 2nd time getting proper response (Edge, Firefox). Thanks!
LoginService (Calls Http post method and returns observable)
login(loginRequest: LoginRequest){
console.log("LoginService.login - userName " + loginRequest.username);
let options = new RequestOptions({ headers: headers });
return this.http.post(this.http_url, loginRequest, options).map( res =>
res.json());
LoginFormComponent (calls service class and convert JSON to typescript object)
onSubmit() {
this.loginSvc.login(this.loginRequest).subscribe(
data => this.loginResponseStr = data,
error => alert(error),
() => console.log('Request completed')
);
var loginResponse = new LoginResponse();
loginResponse.fillFromJSON(JSON.stringify(this.loginResponseStr));
console.log(loginResponse.status);
console.log(loginResponse.statusDesc);
if(loginResponse.status == "SUCCESS"){
this.router.navigate(['/home-page']);
}
Console log
LoginService.login - userName admin main.bundle.js:370:9
undefined main.bundle.js:266:9
undefined main.bundle.js:267:9
Request completed main.bundle.js:263:181
LoginService.login - userName admin main.bundle.js:370:9
SUCCESS main.bundle.js:266:9
VALID USER main.bundle.js:267:9
Request completed main.bundle.js:263:181
Angular server calls are asynchronous, that mean the code wont wait for the server to respond before executing the rest of the code. Such as PHP. So you would not see a blank page waiting for the server to send data. When you want to deal with the respose come from a server call you have to add all the code within the subscribe; that means if this information needed to be passed to another service.
Your code should look like this.
onSubmit() {
this.loginSvc.login(this.loginRequest).subscribe(
data => {
this.loginResponseStr = data
var loginResponse = new LoginResponse();
loginResponse.fillFromJSON(JSON.stringify(data));
console.log(loginResponse.status);
console.log(loginResponse.statusDesc);
if (loginResponse.status == "SUCCESS") {
this.router.navigate(['/home-page']);
}
},
error => alert(error),
() => console.log('Request completed')
);
}
I'm running this little node express server, which is supposed to check if the voucher is valid later and then send an answer back to the client
this is my code
app.post('/voucher', function (request, response) {
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Request-Method', '*');
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
response.setHeader('Access-Control-Allow-Headers', 'authorization, content-type');
if ( request.method === 'OPTIONS' ) {
response.writeHead(200);
response.end();
return;
}
console.log(request)
let results;
let body = [];
request.on('data', function(chunk) {
body.push(chunk);
}).on('end', function() {
results = Buffer.concat(body).toString();
// results = JSON.parse(results);
console.log('#### CHECKING VOUCHER ####', results)
let success = {success: true, voucher: {name: results,
xxx: 10}}
success = qs.escape(JSON.stringify(success))
response.end(success)
} )
}
);
It is obviously just an example and the actual check is not implemented yet. So far so good.
Now on the client side where I work with REACT, I can not seem to decode the string I just send there.
there I'm doing this
var voucherchecker = $.post('http://localhost:8080/voucher', code , function(res) {
console.log(res)
let x = JSON.parse(res)
console.log(x)
console.log(qs.unescape(x))
It gives me the error
Uncaught SyntaxError: Unexpected token % in JSON at position 0
When I do it the other way arround
let x = qs.unescape(res)
console.log(x)
console.log(JSON.parse(x))
Than it tells me
Uncaught TypeError: _querystring2.default.unescape is not a function
Maybe you can help me? I don't know what the issue is here. Thank you.
Also another question on this behalf, since I'm only a beginner. Is there smarter ways to do such things than I'm doing it now? I have react which renders on the client and I have a mini express server which interacts a few times with it during the payment process.
The both run on different ports.
What would be the standard way or best practice to do such things?
I'm a bit perplexed as to why your backend code has so much going on in the request.
Since you asked for if there is a different way to write this, I will share with you how I would write it.
Server
It seems that you want your requests to enable CORS, it also seems that you originally wanted to parse a JSON in your request body.
This is how I would recommend you re-write your endpoint
POST /voucher to take a request with body JSON
{
code: "xxxxx"
}
and respond with
{
success: true,
voucher: {
name: results,
xxx: 10
}
}
I would recommend you use express's middleware feature as you will probably use CORS and parse JSON in most your requests so in your project I would.
npm install body-parser
npm install cors
then in your app initialization
var bodyParser = require('body-parser')
var cors = require('cors')
var app = express()
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json you can choose to just pars raw text as well
app.use(bodyParser.json())
// this will set Access-Control-Allow-Origin * similar for all response headers
app.use(cors())
You can read more about body-parser and cors in their respective repos, if you don't want to use them I would still recommend you use your own middleware in order to reduse future redundancy in your code.
So far this will substitute this part of your code
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Request-Method', '*');
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
response.setHeader('Access-Control-Allow-Headers', 'authorization, content-type');
if ( request.method === 'OPTIONS' ) {
response.writeHead(200);
response.end();
return;
}
console.log(request)
let results;
let body = [];
request.on('data', function(chunk) {
body.push(chunk);
}).on('end', function() {
results = Buffer.concat(body).toString();
// results = JSON.parse(results);
Now your route definition can just be
app.post('/voucher', function (request, response) {
var result = request.body.code // added by body-parser
console.log('#### CHECKING VOUCHER ####', result)
// express 4+ is smart enough to send this as json
response.status(200).send({
success: true,
voucher: {
name: results,
xxx: 10
}
})
})
Client
your client side can then be, assuming $ is jquery's post function
var body = {
code: code
}
$.post('http://localhost:8080/voucher', body).then(function(res) {
console.log(res)
console.log(res.data)
return res.data
})
I have the following function which returns a promise to get data. I am using the request npm module.
let getData = function (user) {
return new Promise(function (resolve, reject) {
let url = 'https://someurl.com/' + user;
request(url, function (error, res, body) {
if (error) reject(error);
try{
resolve(cleanData(JSON.parse(body).items))
}catch(e){
console.log(body)
console.log(url)
console.log(e);
}
})
})
}
When I resolve my promise, I sometimes get something like this:
SyntaxError: Unexpected token u in JSON at position 0
That happens when my body is returned as undefined. But other times I also get something like this:
SyntaxError: Unexpected token f in JSON at position 11203
And other times (most of the times), it goes through perfectly.
I've been able to double check this while debugging and it seems that there are times when I get an incomplete body. I know for a fact that the body from the source url is not incomplete. I checked this by going to the url directly with my browser and making sure the json was complete and valid.
What is going on? It is my understanding that the callback on the request function is only called when the response is ready to be consumed.
No - the callback is triggered as soon as there is a response - see
HTTP request API documentation. You will need to implement a response listener as:
res.on('data', (chunk) => {
// do the data accumulation here
});
res.on('end', () => {
// do the resolve here
});
In request module if you expect binary data from response, then set encoding: null in request options.
Excerpt from:
encoding - encoding to be used on setEncoding of response data. If null, the body is returned as a Buffer. Anything else (including the default value of undefined) will be passed as the encoding parameter to toString() (meaning this is effectively utf8 by default). (Note: if you expect binary data, you should set encoding: null.)
https://www.npmjs.com/package/request