Retrieve Result from Hyperledger Fabric Transaction - javascript

I submitted a transaction to Hyperledger Fabric, but I'd like to get object created by this.
The object that I get from this is Undefined.
Obs: The transaction is successfully created in Hyperledger Fabric.
async submit(resource, method) {
try{
this.businessNetworkDefinition = await this.bizNetworkConnection.connect(cardname);
if (!this.businessNetworkDefinition) {
console.log("Error in network connection");
throw "Error in network connection";
}
let factory = this.businessNetworkDefinition.getFactory();
let transaction = factory.newTransaction(NS, method);
Object.assign(transaction, resource)
return await this.bizNetworkConnection.submitTransaction(transaction);
}catch(error){
console.log(error);
throw error;
}
}

Currently the submitTransaction function is not returning anything. It is a bug or working as intended.
To go into more detail: When you delve through the source code of the composer you will finally get to the following code in composer-connector-hlfv1.
invokeChainCode(securityContext, functionName, args, options) {
const method = 'invokeChainCode';
LOG.entry(method, securityContext, functionName, args, options);
if (!this.businessNetworkIdentifier) {
return Promise.reject(new Error('No business network has been specified for this connection'));
}
// Check that a valid security context has been specified.
HLFUtil.securityCheck(securityContext);
// Validate all the arguments.
if (!functionName) {
return Promise.reject(new Error('functionName not specified'));
} else if (!Array.isArray(args)) {
return Promise.reject(new Error('args not specified'));
}
try {
args.forEach((arg) => {
if (typeof arg !== 'string') {
throw new Error('invalid arg specified: ' + arg);
}
});
} catch(error) {
return Promise.reject(error);
}
let txId = this._validateTxId(options);
let eventHandler;
// initialize the channel if it hasn't been initialized already otherwise verification will fail.
LOG.debug(method, 'loading channel configuration');
return this._initializeChannel()
.then(() => {
// check the event hubs and reconnect if possible. Do it here as the connection attempts are asynchronous
this._checkEventhubs();
// Submit the transaction to the endorsers.
const request = {
chaincodeId: this.businessNetworkIdentifier,
txId: txId,
fcn: functionName,
args: args
};
return this.channel.sendTransactionProposal(request); // node sdk will target all peers on the channel that are endorsingPeer
})
.then((results) => {
// Validate the endorsement results.
LOG.debug(method, `Received ${results.length} result(s) from invoking the composer runtime chaincode`, results);
const proposalResponses = results[0];
let {validResponses} = this._validatePeerResponses(proposalResponses, true);
// Submit the endorsed transaction to the primary orderers.
const proposal = results[1];
const header = results[2];
// check that we have a Chaincode listener setup and ready.
this._checkCCListener();
eventHandler = HLFConnection.createTxEventHandler(this.eventHubs, txId.getTransactionID(), this.commitTimeout);
eventHandler.startListening();
return this.channel.sendTransaction({
proposalResponses: validResponses,
proposal: proposal,
header: header
});
})
.then((response) => {
// If the transaction was successful, wait for it to be committed.
LOG.debug(method, 'Received response from orderer', response);
if (response.status !== 'SUCCESS') {
eventHandler.cancelListening();
throw new Error(`Failed to send peer responses for transaction '${txId.getTransactionID()}' to orderer. Response status '${response.status}'`);
}
return eventHandler.waitForEvents();
})
.then(() => {
LOG.exit(method);
})
.catch((error) => {
const newError = new Error('Error trying invoke business network. ' + error);
LOG.error(method, newError);
throw newError;
});
}
As you can see at the end, all that is happening is waiting for Events and Log.exit which return nothing. So currently you have to get your transaction result in another way.

The only way I could get something from my chaincode is through events. There's native interface that might be able to query for transaction data or something like this, but i haven't looked into it yet.

Related

Firebase Function call returns "NOT_FOUND" exception. (Android, koltin)

I am trying to use Firebase Functions in my koltin Android app to send a message from a client device to another.
Firebase Function within index.js
exports.callUser = functions.https.onCall((data) => {
console.log('Call request received.');
// Message text passed from the client.
const registrationToken = data.registrationToken;
functions.logger.log('Calling', registrationToken);
var message = {
data: {
doctor: 'foo',
patient: 'bar',
room: 'foobar'
},
token: registrationToken
};
return admin.messaging().send(message)
.then((response) => {
console.log('Successfully sent incoming call message:', response);
return "Sent";
})
.catch((error) => {
console.log('Error sending message:', error);
throw new functions.https.HttpsError('unknown', error.message, error);
});
});
Firebase function client call
private fun makeCall(registrationToken: String): Task<String> {
// Create the arguments to the callable function.
val data = hashMapOf(
"registrationToken" to registrationToken
)
Log.d(TAG, "callUser data input: $data")
return Firebase.functions
.getHttpsCallable("callUser")
.call(data)
.continueWith { task ->
val result = task.result?.data as String
result
}
}
Exception Handling from function
makeCall(registrationToken)
.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
val e = task.exception
if (e is FirebaseFunctionsException) {
val code = e.code
val details = e.details
Log.w(TAG, "$code")
}
// [START_EXCLUDE]
Log.w(TAG, "addMessage:onFailure", e)
showSnackbar("An error occurred.")
return#OnCompleteListener
// [END_EXCLUDE]
}
// [START_EXCLUDE]
val result = task.result
Log.d(TAG,"MakeCall result: $result")
val intent = Intent(activity, VideoActivity::class.java)
startActivity(intent)
// [END_EXCLUDE]
})
I have been able to write simple https.onRequest() functions which work as expected but I cannot figure out what I am doing wrong with this callback function.
Logs
W/HomeFragment: NOT_FOUND
W/HomeFragment: addMessage:onFailure
com.google.firebase.functions.FirebaseFunctionsException: NOT_FOUND
at com.google.firebase.functions.FirebaseFunctions$2.onResponse(com.google.firebase:firebase-
functions##19.0.2:281)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
I am using the Firebase emulator to test, there are no logs from that as the function never gets called successfully.
It is because I was using the Firebase Emulator. Before accessing 'Firebase.functions' you must include 'FirebaseFunctions.getInstance().useFunctionsEmulator("http://10.0.2.2:5001")' if you wish to use the emulator.
See: https://firebase.google.com/docs/emulator-suite/connect_functions#callable_functions

How to cache a simple javascript array in Cloudflare and return it to Slack?

I am trying to create a Slack bot that will add a developer to a release queue using a CloudFlare worker. Example: /releasenext <your name>, Slack bot will add you to the queue and will tell you who else is in that queue.
I would like to use Cloudflare cache to cache names coming from the POST request. So I am using a javascript array. I am trying to cache that array, and anytime somebody is adding their name to the queue, the Slack bot should reveal the persons already in the queue and the new person who added their name.
So far, I managed to create the communication between Cloudflare and Slack via a command on Slack /releasenext <your name>, and Slack answers me with: the next person to release is: <your name>.
I found the documentation and some examples but I am not understanding how it works exactly.
here is the documentation: https://workers.cloudflare.com/docs/reference/workers-concepts/using-cache/
And here is my code:
const SLACK_TOKEN = "secret"
const BOT_NAME = "Deploy-bot 🤖"
let jsonHeaders = new Headers([["Content-Type", "application/json"]])
let resp
addEventListener('fetch', event => {
event.respondWith(slackWebhookHandler(event.request))
resp = fetch(event.request, { cf: {cacheEverything: true} })
resp = fetch(event.request, { cf: { cacheTtl: 300 } })
})
// simpleResponse generates a simple JSON response
// with the given status code and message.
function simpleResponse(statusCode, message) {
let resp = {
message: message,
status: statusCode
}
return new Response(JSON.stringify(resp), {
headers: jsonHeaders,
status: statusCode
})
}
// slackResponse builds a message for Slack with the given text
// #param {string} text - the message text to return
function slackResponse(text) {
let content = {
response_type: "in_channel",
text: text
}
try {
return new Response(JSON.stringify(content), {
headers: jsonHeaders,
status: 200
})
} catch (e) {
return simpleResponse(
200,
"Sorry, I had an issue generating a response. Try again in a bit!"
)
}
}
// parseMessage parses the selected name from the Slack message.
// #return {string} - the name.
function parseMessage(message) {
try {
const name = message.get("text").trim()
return {name: name}
} catch (e) {
return null
}
}
async function addPersonToQueue(name) {
try {
let cachedResponse = false
if (resp.headers.get("cf-cache-status").toLowerCase() === "hit") {
cachedResponse = true
}
const nameQueue = []
nameQueue.push({name: name}, {cached: cachedResponse})
let output = nameQueue.map(item => {return item.name}).join(', ')
return output
} catch (e) {
throw new Error(`could not fetch the selected name: ${e}`)
}
}
// slackWebhookHandler handles an incoming Slack webhook and generates a response.
// #param {Request} request
async function slackWebhookHandler(request) {
if (request.method !== "POST") {
return simpleResponse(
200,
`Hi, I'm ${BOT_NAME}, a Slack bot for fetching the latest person name to release`
)
}
let formData
try {
formData = await request.formData()
if (formData.get("token").toString() !== SLACK_TOKEN) {
return simpleResponse(403, "Invalid Slack verification token")
}
} catch (e) {
return simpleResponse(400, "could not decode POST form data")
}
try {
let parsed = parseMessage(formData)
if (parsed === null) {
throw new Error("could not parse your message")
}
let reply = await addPersonToQueue(parsed.name)
return slackResponse(
`The next person to release is: *${reply}*`,
`the cache is: ${reply.cached}`
)
} catch (e) {
return simpleResponse(
200,
`Sorry, I had an issue retrieving names from the release queue ${e}`
)
}
}
The error message I have now is
{"message":"Sorry, I had an issue retrieving names from the release queue Error: could not fetch the selected name: TypeError: Cannot read property 'get' of undefined","status":200}
The expected output is: The next person to release is: person1, <your name>.
Assuming that another person already added their name to the queue first.

Axios Interceptors retry original request and access original promise

I have an interceptor in place to catch 401 errors if the access token expires. If it expires it tries the refresh token to get a new access token. If any other calls are made during this time they are queued until the access token is validated.
This is all working very well. However when processing the queue using Axios(originalRequest) the originally attached promises are not being called. See below for an example.
Working interceptor code:
Axios.interceptors.response.use(
response => response,
(error) => {
const status = error.response ? error.response.status : null
const originalRequest = error.config
if (status === 401) {
if (!store.state.auth.isRefreshing) {
store.dispatch('auth/refresh')
}
const retryOrigReq = store.dispatch('auth/subscribe', token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token
Axios(originalRequest)
})
return retryOrigReq
} else {
return Promise.reject(error)
}
}
)
Refresh Method (Used the refresh token to get a new access token)
refresh ({ commit }) {
commit(types.REFRESHING, true)
Vue.$http.post('/login/refresh', {
refresh_token: store.getters['auth/refreshToken']
}).then(response => {
if (response.status === 401) {
store.dispatch('auth/reset')
store.dispatch('app/error', 'You have been logged out.')
} else {
commit(types.AUTH, {
access_token: response.data.access_token,
refresh_token: response.data.refresh_token
})
store.dispatch('auth/refreshed', response.data.access_token)
}
}).catch(() => {
store.dispatch('auth/reset')
store.dispatch('app/error', 'You have been logged out.')
})
},
Subscribe method in auth/actions module:
subscribe ({ commit }, request) {
commit(types.SUBSCRIBEREFRESH, request)
return request
},
As well as the Mutation:
[SUBSCRIBEREFRESH] (state, request) {
state.refreshSubscribers.push(request)
},
Here is a sample action:
Vue.$http.get('/users/' + rootState.auth.user.id + '/tasks').then(response => {
if (response && response.data) {
commit(types.NOTIFICATIONS, response.data || [])
}
})
If this request was added to the queue I because the refresh token had to access a new token I would like to attach the original then():
const retryOrigReq = store.dispatch('auth/subscribe', token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token
// I would like to attache the original .then() as it contained critical functions to be called after the request was completed. Usually mutating a store etc...
Axios(originalRequest).then(//if then present attache here)
})
Once the access token has been refreshed the queue of requests is processed:
refreshed ({ commit }, token) {
commit(types.REFRESHING, false)
store.state.auth.refreshSubscribers.map(cb => cb(token))
commit(types.CLEARSUBSCRIBERS)
},
Update Feb 13, 2019
As many people have been showing an interest in this topic, I've created the axios-auth-refresh package which should help you to achieve behaviour specified here.
The key here is to return the correct Promise object, so you can use .then() for chaining. We can use Vuex's state for that. If the refresh call happens, we can not only set the refreshing state to true, we can also set the refreshing call to the one that's pending. This way using .then() will always be bound onto the right Promise object, and be executed when the Promise is done. Doing it so will ensure you don't need an extra queue for keeping the calls which are waiting for the token's refresh.
function refreshToken(store) {
if (store.state.auth.isRefreshing) {
return store.state.auth.refreshingCall;
}
store.commit('auth/setRefreshingState', true);
const refreshingCall = Axios.get('get token').then(({ data: { token } }) => {
store.commit('auth/setToken', token)
store.commit('auth/setRefreshingState', false);
store.commit('auth/setRefreshingCall', undefined);
return Promise.resolve(true);
});
store.commit('auth/setRefreshingCall', refreshingCall);
return refreshingCall;
}
This would always return either already created request as a Promise or create the new one and save it for the other calls. Now your interceptor would look similar to the following one.
Axios.interceptors.response.use(response => response, error => {
const status = error.response ? error.response.status : null
if (status === 401) {
return refreshToken(store).then(_ => {
error.config.headers['Authorization'] = 'Bearer ' + store.state.auth.token;
error.config.baseURL = undefined;
return Axios.request(error.config);
});
}
return Promise.reject(error);
});
This will allow you to execute all the pending requests once again. But all at once, without any querying.
If you want the pending requests to be executed in the order they were actually called, you need to pass the callback as a second parameter to the refreshToken() function, like so.
function refreshToken(store, cb) {
if (store.state.auth.isRefreshing) {
const chained = store.state.auth.refreshingCall.then(cb);
store.commit('auth/setRefreshingCall', chained);
return chained;
}
store.commit('auth/setRefreshingState', true);
const refreshingCall = Axios.get('get token').then(({ data: { token } }) => {
store.commit('auth/setToken', token)
store.commit('auth/setRefreshingState', false);
store.commit('auth/setRefreshingCall', undefined);
return Promise.resolve(token);
}).then(cb);
store.commit('auth/setRefreshingCall', refreshingCall);
return refreshingCall;
}
And the interceptor:
Axios.interceptors.response.use(response => response, error => {
const status = error.response ? error.response.status : null
if (status === 401) {
return refreshToken(store, _ => {
error.config.headers['Authorization'] = 'Bearer ' + store.state.auth.token;
error.config.baseURL = undefined;
return Axios.request(error.config);
});
}
return Promise.reject(error);
});
I haven't tested the second example, but it should work or at least give you an idea.
Working demo of first example - because of the mock requests and demo version of service used for them, it will not work after some time, still, the code is there.
Source: Interceptors - how to prevent intercepted messages to resolve as an error
Why not try something like this ?
Here I use AXIOS interceptors in both directions. For the outgoing direction I set the Authorization header. For the incoming direction - if there is an error, I return a promise (and AXIOS will try to resolve it). The promise checks what the error was - if it was 401 and we see it for the first time (i.e. we are not inside the retry) then I try to refresh the token. Otherwise I throw the original error.
In my case refreshToken() uses AWS Cognito but you can use whatever suits you most. Here I have 2 callbacks for refreshToken():
when the token is successfully refreshed, I retry the AXIOS request using an updated config - including the new fresh token and setting a retry flag so that we do not enter an endless cycle if the API repeatedly responds with 401 errors. We need to pass the resolve and reject arguments to AXIOS or otherwise our fresh new promise will be never resolved/rejected.
if the token could not be refreshed for any reason - we reject the promise. We can not simply throw an error because there might be try/catch block around the callback inside AWS Cognito
Vue.prototype.$axios = axios.create(
{
headers:
{
'Content-Type': 'application/json',
},
baseURL: process.env.API_URL
}
);
Vue.prototype.$axios.interceptors.request.use(
config =>
{
events.$emit('show_spin');
let token = getTokenID();
if(token && token.length) config.headers['Authorization'] = token;
return config;
},
error =>
{
events.$emit('hide_spin');
if (error.status === 401) VueRouter.push('/login'); // probably not needed
else throw error;
}
);
Vue.prototype.$axios.interceptors.response.use(
response =>
{
events.$emit('hide_spin');
return response;
},
error =>
{
events.$emit('hide_spin');
return new Promise(function(resolve,reject)
{
if (error.config && error.response && error.response.status === 401 && !error.config.__isRetry)
{
myVue.refreshToken(function()
{
error.config.__isRetry = true;
error.config.headers['Authorization'] = getTokenID();
myVue.$axios(error.config).then(resolve,reject);
},function(flag) // true = invalid session, false = something else
{
if(process.env.NODE_ENV === 'development') console.log('Could not refresh token');
if(getUserID()) myVue.showFailed('Could not refresh the Authorization Token');
reject(flag);
});
}
else throw error;
});
}
);
This could be done with a single interceptor:
let _refreshToken = '';
let _authorizing: Promise<void> | null = null;
const HEADER_NAME = 'Authorization';
axios.interceptors.response.use(undefined, async (error: AxiosError) => {
if(error.response?.status !== 401) {
return Promise.reject(error);
}
// create pending authorization
_authorizing ??= (_refreshToken ? refresh : authorize)()
.finally(() => _authorizing = null)
.catch(error => Promise.reject(error));
const originalRequestConfig = error.config;
delete originalRequestConfig.headers[HEADER_NAME]; // use from defaults
// delay original requests until authorization has been completed
return _authorizing.then(() => axios.request(originalRequestConfig));
});
The rest is an application specific code:
Login to api
Save/load auth data to/from storage
Refresh token
Check out the complete example.

Wait for AWS SNS publish callback to return a value to calling method

I am attempting to send a text message when a user requests to reset their password. I would like to wait for the message to be sent to alert the user if it was successful or not. I am currently attempting to do it as follows:
async function sendResetPasswordTextMessage(req, res) {
let result = {};
let phoneNumber = req.body.phoneNumber;
if (phoneNumber === undefined) {
return sendInvalidParametersMessage(res);
}
phoneNumber = phoneNumber.toString();
const userProfile = await models.UserProfile.findOne({
where: {
phoneNumber: phoneNumber
}
});
************************** RELEVANT CODE TO ISSUE *************************
if (userProfile) {
const message = "Your username is:\n" + userProfile.username;
const sent = await AWSSNSClient.sendMessage(message, phoneNumber);
if (!sent) {
result.error = setTitleAndMessage("Error", "An error occurred");
} else {
result.success = setTitleAndMessage("Success", "Message sent");
}
}
return res.send(result);
***************************************************************************
}
In my other class AWSSNSClient, I have the following sendMessage function:
function sendMessage(message, phoneNumber) {
const params = {
Message: message,
MessageStructure: "string",
PhoneNumber: "+1" + phoneNumber
};
let sent = false;
sns.publish(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
sent = true;
}
});
return sent;
}
I cannot figure out how to make sendMessage wait for sns.publish to return before it returns itself. I have tried making it an async method and adding await on sns.publish, but the function still returns before sent gets set to true.
I know that the messages are sending without error because I am receiving them and no console logs are printed.
Stumbled on this one via Google trying to figure this out myself today - short answer that I am now using:
You can now do this with Async/Await — and Call the AWS service (SNS for example) with a .promise() extension to tell aws-sdk to use the promise-ified version of that service function (SNS) instead of the call back based version.
The only caveat here is the containing function must ALSO be async to utilize the await syntax.
For example:
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn
}, async function (err, data) {
if (err) {
console.log("SNS Push Failed:");
console.log(err.stack);
return;
}
console.log('SNS push suceeded: ' + data);
return data;
}).promise();
The important part is the .promise() on the end there. Full docs on using aws-sdk in an async / promise based manner can be found here: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html
In order to run another aws-sdk task you would similarly add await and the .promise() extension to that function (assuming that is available).
For anyone who runs into this thread and is actually looking to simply push multiple aws-sdk promises to an array and wait for that WHOLE array to finish (without regard to which promise executes first) I ended up with something like this:
let snsPromises = [] // declare array to hold promises
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn
}, async function (err, data) {
if (err) {
console.log("Search Push Failed:");
console.log(err.stack);
return;
}
console.log('Search push suceeded: ' + data);
return data;
}).promise();
snsPromises.push(snsResult)
await Promise.all(snsPromises)
Hope that helps someone that randomly stumbles on this via google like I did!
stackdave will that actually wait?
Necevil "Search push suceeded will get logged twice" because you're mixing calling operations by passing a callback and using promises. You should only use one method of getting the result
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn}).promise()
will do the trick
You can simply use callbacks for that. Modify your sendMessge like this
function sendMessage(message, phoneNumber, cb) {
const params = {
Message: message,
MessageStructure: "string",
PhoneNumber: "+1" + phoneNumber
};
sns.publish(params, cb);
}
then on your main file you can supply callback like this
if (userProfile) {
const message = "Your username is:\n" + userProfile.username;
AWSSNSClient.sendMessage(message, phoneNumber, (err, data) => {
if (err) {
result.error = setTitleAndMessage("Error", "An error occurred");
}
else {
result.success = setTitleAndMessage("Success", "Message sent");
}
res.send(result);
});
}
Here the right updated API, August 2018, Necevil answer send the sms twice.
// using config.env
AWS.config.region = 'eu-west-1';
AWS.config.update({
accessKeyId: process.env.AMAZON_SMS_ID,
secretAccessKey: process.env.AMAZON_SMS_TOKEN,
});
// parameters
let params = {
Message: contentSMS, // here your sms
PhoneNumber: mobile, // here the cellphone
};
const snsResult = await sns.publish(params, async (err, data) => {
if (err) {
console.log("ERROR", err.stack);
}
console.log('SNS ok: ' , JSON.stringify (data));
});
If you're having issues with duplicate SNS messages being sent, I fixed this issue by utilizing examples from AWS:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'REGION'});
// Create publish parameters
var params = {
Message: 'MESSAGE_TEXT', /* required */
TopicArn: 'TOPIC_ARN'
};
// Create promise and SNS service object
var publishTextPromise = new AWS.SNS({apiVersion: '2010-03-31'}).publish(params).promise();
// Handle promise's fulfilled/rejected states
publishTextPromise.then(
function(data) {
console.log("Message ${params.Message} send sent to the topic ${params.TopicArn}");
console.log("MessageID is " + data.MessageId);
}).catch(
function(err) {
console.error(err, err.stack);
});
By utilizing a traditional .then() I was able to squash the duplicate message bug mentioned in comments above.
You can create a async function what use the promise method
async function sendMessage(message, phoneNumber){
const params = {
Message: message,
PhoneNumber: phoneNumber
};
return new Promise((resolve, reject) => {
SNS.publish(params, (err, data) => {
if (err) {
console.log("Search Push Failed:");
console.log(err.stack);
return reject(err);
} else {
console.log('Search push suceeded:' + phoneNumber);
return resolve(data);
}
})
});
}
and then you can call
var s= await sendMessage(message,phoneNumber);

How to add results from a promise based API call with message.addReply using Recast.ai?

I'm making a bot that searches restaurants based on location. Can anyone help me why this doesnt show up in FB messenger?:
restaurants(result.getMemory('location').raw)
.then(res=>{
message.addReply(res);
message.reply();
});
}
The call to the restaurants function returns the results from a YELP API call (an array of restaurants) but when I add it as a reply to message, nothing happens in FB messenger.
Here is the full code for message.js:
const recastai = require('recastai');
const restaurants = require('./restaurants');
// This function is the core of the bot behaviour
const replyMessage = (message) => {
// Instantiate Recast.AI SDK, just for request service
const request = new recastai.request(process.env.REQUEST_TOKEN,
process.env.LANGUAGE);
// Get text from message received
const text = message.content;
console.log('I receive: ', text);
// Get senderId to catch unique conversation_token
const senderId = message.senderId;
// Call Recast.AI SDK, through /converse route
request.converseText(text, { conversationToken: senderId })
.then(result => {
//Recast takes text analyses that, returns a result object, generates replies adds messages to reply stack and then sends the replies
//Call Yelp API with when the intent is Location. When Yelp returns result we add it to the result.replies array.
//Then we add everything in result.replies to the messaging queue that sends the responses to FB
if (result.action) {
console.log('The conversation action is: ', result.action.slug);
}
// If there is not any message return by Recast.AI for this current conversation
if (!result.replies.length) {
message.addReply({ type: 'text', content: 'I don\'t have the reply to this yet :)' });
} else {
// Add each reply received from API to replies stack
result.replies.forEach(replyContent => message.addReply({ type: 'text', content: replyContent }));
}
// Send all replies
message.reply()
//send initial reply generated by Recast first
.then(() => {
//call restaurant function that returns a list of results from API
//if the action is location and done
if(result.action && result.action.slug === 'location' && result.action.done){
restaurants(result.getMemory('location').raw)
.then(res=>{
console.log(res);
message.addReply(res);
message.reply();
});
}
})
.catch(err => {
console.error('Error while sending message to channel', err);
});
})
.catch(err => {
console.error('Error while sending message to Recast.AI', err);
});
};
module.exports = replyMessage;
And here is my restaurants.js code that is imported into the message.js file for the bot behavior:
const rp = require('request-promise');
// Load configuration
require('./config');
const restaurants = (location) => {
return Promise.all([
yelpCall(location)
]).then(result => {
//result contains the return value from Yelp call
return result;
});
};
const yelpCall = (location) => {
const auth = {
method: 'POST',
url: 'https://api.yelp.com/oauth2/token?grant_type=client_credentials&client_id='+ process.env.YELP_APP_ID +'&client_secret='+process.env.APP_SECRET
};
return rp(auth)
.then(result => {
const tokens = JSON.parse(result);
return tokens;
})
.then(result=>{
const options = {
url: 'https://api.yelp.com/v3/businesses/search?location=' + location + "&term=thai",
headers: {Authorization: "Bearer " + result.access_token}
};
return rp(options).then(findings =>{
return findings;
});
});
};
module.exports = restaurants;
A few thoughts :
message.reply is thenable, therefore return message.reply() in two places.
request.converseText() is thenable, therefore return request.converseText(...).
restaurants is thenable, therefore return restaurants(...).
in message.js, message.addReply() is passed object of the form {type:..., content:...} in two places but finally just res. Is that correct?
in restaurants.js, Promise.all() appears to be unnecessary. It will cause its result to be wrapped in an array. module.exports = location => yelpCall(location); seems more appropriate.

Categories

Resources