How to automatically join conversation in Microsoft web chat (bot framework) - javascript

I'm using https://github.com/Microsoft/BotFramework-WebChat/blob/master/README.md
I want the bot to receive a "join" message whenever the web-chat widget is displayed on the site.
The idea is that the human does not have to initiate the conversation. The bot can respond to somebody joining the conversation with a welcome message.
How is this possible?

This "Welcome feature" has been a long term discussion and topic since Webchat v3. It looks like it has been fixed 2 days ago with this pull request: https://github.com/Microsoft/BotFramework-WebChat/pull/1286
There is now a sample on how to do that, located here:
https://github.com/Microsoft/BotFramework-WebChat/blob/master/samples/15.d.backchannel-send-welcome-event/index.html
In a few words, the demo is the following:
(async function () {
// In this demo, we are using Direct Line token from MockBot.
// To talk to your bot, you should use the token exchanged using your Direct Line secret.
// You should never put the Direct Line secret in the browser or client app.
// https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();
// We are using a customized store to add hooks to connect event
const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
// When we receive DIRECT_LINE/CONNECT_FULFILLED action, we will send an event activity using WEB_CHAT/SEND_EVENT
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: { language: window.navigator.language }
}
});
}
return next(action);
});
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
Please note that due to the fact that this PR is quite new, it's not embedded in the latest release so you have to point to the master version of webchat.js file, not latest:
<script src="https://cdn.botframework.com/botframework-webchat/master/webchat.js"></script>
And it's working: your bot side is notified of an activity of type Event, where you will be able to reply to your user, before he typed anything:

Related

How to send messages after one hour using React and Firebase

I'm building a chat app with React and Firebase and I'm using functional React components. Please I would like to find out if there is a way to send a user an email if a message has not been read after one hour. I'm using triggerEmail to send emails when a property is created like this:
const formik = useFormik({
... //some other code here
onSubmit: async (values) => {
... //some other code here
await addDoc(collection(db, "mail"), {
to: values.email,
template: {
name: 'Property Uploaded',
data: {
id: values.id
}
}
})
}
})
This code runs when a property is created and an email is sent to the user as well as the admin. There's also a chat feature where users can send messages to the admin and this is wrapped in a custom react hook.
export default function useSendMessage() {
const [error, setError] = useState(null)
const { user } = useAuthContext()
const sendMessage = async (admin, agent, message) => {
setError(null)
try {
await addDoc(collection(db, "messages", admin.id, "messages"), {
admin: admin,
agent: agent,
isSender: admin.id === user.uid,
message: message.trim(),
markRead: false,
created_at: moment().format()
})
await addDoc(collection(db, "messages", agent.id, "messages"), {
admin: admin,
agent: agent,
isSender: agent.id === user.uid,
message: message.trim(),
markRead: false,
created_at: moment().format()
})
} catch (error) {
setError(error.message)
}
}
return { error, sendMessage }
}
And I'm using this hook in the chat app like this:
import useSendMessage from "../../hooks/useSendMessage";
export default function UserMessageInput({ admin, agent }) {
...//some code here
const handleSubmit = async (e) => {
e.preventDefault()
setSending(true)
setMessage("")
await sendMessage(admin, agent, message)
.then(() => {
setSending(false)
})
}
return(
...//chat app UI
)
}
Is there a way to trigger the email service if markRead is false after one hour? I would like to notify the admin or the agent that they have a new message on the site. I'm not sure setTimeout or setInterval can work because the agent or the admin might be offline at certain times.
You can schedule a Cloud Function to run in exactly one hour after the message doc has been created, as explained in this article titled "How to schedule a Cloud Function to run in the future with Cloud Tasks (to build a Firestore document TTL)".
Concretely, in the HTTP callback function that is invoked by Cloud Tasks you will first check the value of the markRead boolean field in the Firestore document and, if it is false, you'll send the email by creating a doc in the mail collection, since you use the Email extension.
Another approach would be to use a scheduled Cloud Function to run e.g. every minute, checking is the message was create more than one hour ago and is not marked as read. The above referred article explains the drawbacks of this approach.

How can I delay a POST request from a user form until AFTER a webhook POST from Stripe is received?

I want users to pay a fee before a POST request from a front end form is processed. I have a Stripe webhook that works fine on the backend, but I'm not sure how to delay the front end posting of the form until after the payment confirmation is received.
In the code below, right now, createTour and createTourPay run at the same time. I would like for createTourPay to execute first, and the createTour only triggers after Stripe posts to my application from the webhook. How can I achieve this?
Controller File (webhook):
exports.webhookCheckout = (req, res, next) => {
const signature = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook error: ${err.message}`);
}
if (
event.type === 'checkout.session.completed' &&
event.line_items.name === 'New Job Purchase'
) {
res.status(200).json({ recieved: true });
// Somehow, I want this to trigger the execution of the POST request in my front end JS file.
} else {
if (event.type === 'checkout.session.completed')
createBookingCheckout(event.data.object);
res.status(200).json({ recieved: true });
}
};
Front end JS file:
export const createTourPay = async myForm => {
try {
// 1) Get the checkout session from API response
const session = await axios(`/api/v1/tours/tour-pay`);
const complete = 1;
// console.log(session);
// 2) Create checkout form + charge the credit card
await stripe.redirectToCheckout({
sessionId: session.data.session.id
});
} catch (err) {
// console.log(err);
showAlert('error', err);
}
};
export const createTour = async myForm => {
try {
const startLocation = {
type: 'Point',
coordinates: [-10.185942, 95.774772],
address: '123 Main Street',
description: 'Candy Land'
};
const res = await axios({
method: 'POST',
headers: {
'Content-Type': `multipart/form-data; boundary=${myForm._boundary}`
},
url: '/api/v1/tours',
data: myForm
});
if (res.data.status === 'success') {
showAlert('success', 'NEW TOUR CREATED!');
window.setTimeout(() => {
location.assign('/');
}, 1500);
}
} catch (err) {
showAlert('error', err.response.data.message);
}
};
Broadly: don't do this. Instead, you in fact should create some pending/unpaid version of the "tour" (or any other product/service) in your system, then attach the unique id (eg: tour_123) to the Checkout session when you create it, either using the client_reference_id (doc) or metadata (doc):
const session = await stripe.checkout.sessions.create({
// ... other params
client_reference_id: 'tour_123',
metadata: { tour_id: 'tour_123' },
});
Then you'd use the webhook to inspect those values, and update your own database to indicate the payment has been made and that you can fulfill the order to the customer (ship product, send codes, allow access to service etc).
If you really want to proceed with a more synchronous flow, you can use separate auth and capture to sequence your customer experience and capture the funds later after authorizing and creating your tour entity.
Edit: a note about security
You should never trust client-side logic for restricted operations like creating a "paid" tour. A motivated user could, for example, simply call your /api/v1/tours create endpoint without ever going through your payment flow. Unless you validate a payment and track that state on your server you won't be able to know which of these had actually paid you.

How can I process Apple pay in react native

It's my first time to use apple pay. I'm using react-native-payments library and I could get paymentToken but I cant understand how I can perform a payment process.
The payment provider companey hyperpay provided me entity Id and Access token but I don't know how to use them, which information I'll send, for any link I will send information and which response I'll get. This is the function does Payment process as documentation mentioned
paymentRequest.show()
.then(paymentResponse => {
const { paymentToken } = paymentResponse.details;
return fetch('...', {
method: 'POST',
body: {
paymentToken
}
})
.then(res => res.json())
.then(successHandler)
.catch(errorHandler);
});

How to integrate Dialogflow v2 with javascript frontend (Vue.js)

I'm trying to integrate Dialogflow with Vue.js (and axios) according to the documentation's sample HTTP request: https://dialogflow.com/docs/reference/v2-auth-setup and detectIntent: https://dialogflow.com/docs/reference/api-v2/rest/v2beta1/projects.agent.sessions/detectIntent.
I have a service account set up with sufficient permissions, and given it the path parameters and request body as shown in the documentation, but I keep getting 'Error: Request failed with status code 400' when calling the detectIntent API.
There are a few things I'm not sure of, though:
How do I get a sessionId? Currently I just copy the sessionId from Firebase Function logs which shows up when entering a query through the Dialogflow console directly.
How do I actually implement $(gcloud auth print-access-token) in javascript code? Currently I'm running the command in the terminal and pasting the token in the code, just to test if the API works, but I have no clue how it should be implemented.
(Perhaps useful, I have fulfillment set up in a functions folder, and that is working nicely.)
Thanks in advance!
<script>
import axios from 'axios'
export default {
name: 'myChatBot',
mounted () {
// Authorization: Bearer $(gcloud auth print-access-token)
const session = 'projects/mychatbot/agent/sessions/some-session-id'
const token = 'xxxxxxxxxxxx'
axios.defaults.baseURL = 'https://dialogflow.googleapis.com'
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
axios.defaults.headers.post['Content-Type'] = 'application/json'
axios
.post(`/v2beta1/${session}:detectIntent`, {
"queryInput": {
"text": "add buy milk to inbox",
"languageCode": "en-US"
}
})
.then(response => console.log(response))
.catch(error => console.log(error))
}
}
</script>
You can use JWT authorization to handle your #2 question. You just need to put your JSON file someplace safe. https://developers.google.com/identity/protocols/OAuth2ServiceAccount#jwt-auth
The reason you are getting the code 400 is because your params are a little off. Here is how your post should look (I've also added some extra code to handle token generation):
<script>
import axios from 'axios'
import { KJUR } from 'jsrsasign'
const creds = require('./YOUR_JSON_FILE')
export default {
name: 'myChatBot',
data() {
return {
token: undefined,
tokenInterval: undefined
}
},
created() {
// update the tokens every hour
this.tokenInterval = setInterval(this.generateToken, 3600000)
this.generateToken()
},
mounted () {
this.detectIntent('add buy milk to inbox')
},
beforeDestroy() {
clearInterval(this.tokenInterval)
},
methods: {
generateToken() {
// Header
const header = {
alg: 'RS256',
typ: 'JWT',
kid: creds.private_key_id
}
// Payload
const payload = {
iss: creds.client_email,
sub: creds.client_email,
iat: KJUR.jws.IntDate.get('now'),
exp: KJUR.jws.IntDate.get('now + 1hour'),
aud: 'https://dialogflow.googleapis.com/google.cloud.dialogflow.v2.Sessions'
}
const stringHeader = JSON.stringify(header)
const stringPayload = JSON.stringify(payload)
this.token = KJUR.jws.JWS.sign('RS256', stringHeader, stringPayload, creds.private_key)
},
detectIntent(text, languageCode = 'en-US') {
if (!this.token) {
// try again
setTimeout(this.detectIntent, 300, text, languageCode)
return
}
// error check for no text, etc.
const session = 'projects/mychatbot/agent/sessions/some-session-id'
axios.defaults.baseURL = 'https://dialogflow.googleapis.com'
axios.defaults.headers.common['Authorization'] = `Bearer ${this.token}`
axios.defaults.headers.post['Content-Type'] = 'application/json'
axios
.post(`/v2beta1/${session}:detectIntent`, {
queryInput: {
text: {
text,
languageCode
}
}
})
.then(response => console.log(response))
.catch(error => console.log(error))
}
}
}
</script>
You can see that in QueryInput it's taking 1 of 3 different types of objects ("text" being one of those).
In the link, it's stated under the HTTP request session path parameters that "It's up to the API caller to choose an appropriate session id.
It can be a random number or some type of user identifier (preferably hashed).
For integration with Dialogflow V2, here's an example for doing with third-party tools that are easy to integrate and start using.
The sessionId is an identifier you can provide that will indicate to Dialogflow whether subsequent requests belong to the same "session" of user interaction (see docs).
For a client's first request to the API, you could just generate a random number to use as a session ID. For subsequent requests from the same client (e.g. if a user is continuing to converse with your agent) you can reuse the same number.
Your implementation of the token management looks fine, as long as the service account you are using has appropriately limited access (since this token could potentially allow anyone to make requests to Google Cloud APIs). For additional security, you could consider proxying the request to Dialogflow through your own server rather than making the call from the client.

Sending data / payload to the Google Chrome Push Notification with Javascript

I'm working on the Google Chrome Push Notification and I'm trying to send the payload to the google chrome worker but, I have no idea how I receive this payload.
I have an API to create and save the notifications in my database and I need send the values through the https://android.googleapis.com/gcm/send and receive on the worker.js
This is my worker.js
self.addEventListener('push', function(event) {
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = '/images/icon-192x192.png';
var tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
});
And this is how I'm calling the GCM
curl --header "Authorization: key=AIzaSyDQjYDxeS9MM0LcJm3oR6B7MU7Ad2x2Vqc" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d "{ \"data\":{\"foo\":\"bar\"}, \"registration_ids\":[\"APA91bGqJpCmyCnSHLjY6STaBQEumz3eFY9r-2CHTtbsUMzBttq0crU3nEXzzU9TxNpsYeFmjA27urSaszKtA0WWC3yez1hhneLjbwJqlRdc_Yj1EiqLHluVwHB6V4FNdXdKb_gc_-7rbkYkypI3MtHpEaJbWsj6M5Pgs4nKqQ2R-WNho82mnRU\"]}"
I tried to get event.data but, this is undefined.
Does anyone have any idea or sugestion?
Unfortunately it seems like an intended behavior:
A downside to the current implementation of the Push API in Chrome is
that you can’t send a payload with a push message. Nope, nothing. The
reason for this is that in a future implementation, payload will have
to be encrypted on your server before it’s sent to a push messaging
endpoint. This way the endpoint, whatever push provider it is, will
not be able to easily view the content of the push payload. This also
protects against other vulnerabilities like poor validation of HTTPS
certificates and man-in-the-middle attacks between your server and the
push provider. However, this encryption isn’t supported yet, so in the
meantime you’ll need to perform a fetch request to get information
needed to populate a notification.
As stated above, the workaround is to contact back your backend after receiving the push and fetch the stored data on the 3rd party server.
#gauchofunky's answer is correct. With some guidance from the folks on the Chromium dev slack channel and #gauchofunky I was able to piece something together. Here's how to work around the current limitations; hopefully my answer becomes obsolete soon!
First figure out how you're going to persist notifications on your backend. I'm using Node/Express and MongoDB with Mongoose and my schema looks like this:
var NotificationSchema = new Schema({
_user: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
subscriptionId: String,
title: String,
body: String,
sent: { type: Boolean, default: false }
});
Be sure to add an icon if you'd like to alter the icon. I use the same icon every time so mine's hardcoded in the service worker.
Figuring out the correct REST web service took some thought. GET seemed like an easy choice but the call to get a notification causes side effects, so GET is out. I ended up going with a POST to /api/notifications with a body of {subscriptionId: <SUBSCRIPTION_ID>}. Within the method we basically perform a dequeue:
var subscriptionId = req.body.subscriptionId;
Notification
.findOne({_user: req.user, subscriptionId: subscriptionId, sent: false})
.exec(function(err, notification) {
if(err) { return handleError(res, err); }
notification.sent = true;
notification.save(function(err) {
if(err) { return handleError(res, err); }
return res.status(201).json(notification);
});
});
In the service worker we need to for sure get the subscription before we make the fetch.
self.addEventListener('push', function(event) {
event.waitUntil(
self.registration.pushManager.getSubscription().then(function(subscription) {
fetch('/api/notifications/', {
method: 'post',
headers: {
'Authorization': 'Bearer ' + self.token,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then(function(response) { return response.json(); })
.then(function(data) {
self.registration.showNotification(data.title, {
body: data.body,
icon: 'favicon-196x196.png'
});
})
.catch(function(err) {
console.log('err');
console.log(err);
});
})
);
});
It's also worth noting that the subscription object changed from Chrome 43 to Chrome 45. In Chrome 45 the subscriptionId property was removed, just something to look out for - this code was written to work with Chrome 43.
I wanted to make authenticated calls to my backend so I needed to figure out how to get the JWT from my Angular application to my service worker. I ended up using postMessage. Here's what I do after registering the service worker:
navigator.serviceWorker.register('/service-worker.js', {scope:'./'}).then(function(reg) {
var messenger = reg.installing || navigator.serviceWorker.controller;
messenger.postMessage({token: $localStorage.token});
}).catch(function(err) {
console.log('err');
console.log(err);
});
In the service worker listen for the message:
self.onmessage.addEventListener('message', function(event) {
self.token = event.data.token;
});
Strangely enough, that listener works in Chrome 43 but not Chrome 45. Chrome 45 works with a handler like this:
self.addEventListener('message', function(event) {
self.token = event.data.token;
});
Right now push notifications take quite a bit of work to get something useful going - I'm really looking forward to payloads!
Actually, payload should be implemented in Chrome 50 (release date - April 19, 2016). In Chrome 50 (and in the current version of Firefox on desktop) you can send some arbitrary data along with the push so that the client can avoid making the extra request. All payload data must be encrypted.
Here is the the encryption details from developer : https://developers.google.com/web/updates/2016/03/web-push-encryption?hl=en
I just ran into this problem. Newer versions of firefox and chrome( version 50+) support payload transferring. The dev docs here details the implementation on how this works. An important thing to note is that google GCM or possibly client/chome (I dont know which one) will actually ignore the payload entirely if it is not encrypted.
This website has both client/server implementations of how to do the push and retrieval through service workers. The push library that examples use is merely a wrapper around a normal REST call
service worker example implementation:
self.addEventListener('push', function(event) {
var payload = event.data ? event.data.text() : 'no payload';
event.waitUntil(
self.registration.showNotification('ServiceWorker Cookbook', {
body: payload,
})
);
});
Server example implementation:
var webPush = require('web-push');
webPush.setGCMAPIKey(process.env.GCM_API_KEY);
module.exports = function(app, route) {
app.post(route + 'register', function(req, res) {
res.sendStatus(201);
});
app.post(route + 'sendNotification', function(req, res) {
setTimeout(function() {
webPush.sendNotification(req.body.endpoint, {
TTL: req.body.ttl,
payload: req.body.payload,
userPublicKey: req.body.key,
userAuth: req.body.authSecret,
}).then(function() {
res.sendStatus(201);
});
}, req.body.delay * 1000);
});
};
Client side javascript implementation example of printing out the the required fields.
navigator.serviceWorker.register('serviceWorker.js')
.then(function(registration) {
return registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription;
}
return registration.pushManager.subscribe({
userVisibleOnly: true
});
});
}).then(function(subscription) {
var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
endpoint = subscription.endpoint;
console.log("Endpoint: " + endpoint);
console.log("Key: " + key);
console.log("AuthSecret: " + authSecret);
});
To retrieve that data, you need to parse "event.data.text()" to a JSON object. I'm guessing something was updated since you tried to get this to work, but it works now. Unlucky!
However, since I made it to this post when searching for a solution myself, others would probably like a working answer. Here it is:
// Push message event handler
self.addEventListener('push', function(event) {
// If true, the event holds data
if(event.data){
// Need to parse to JSON format
// - Consider event.data.text() the "stringify()"
// version of the data
var payload = JSON.parse(event.data.text());
// For those of you who love logging
console.log(payload);
var title = payload.data.title;
var body = payload.data.body;
var icon = './assets/icons/icon.ico'
var tag = 'notification-tag';
// Wait until payload is fetched
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: {} // Keeping this here in case I need it later
})
);
} else {
console.log("Event does not have data...");
}
}); // End push listener
// Notification Click event
self.addEventListener('notificationclick', function(event) {
console.log("Notification Clicked");
}); // End click listener
Personally, I will be creating a "generic" notification in case my data is funky, and will also be using try/catch. I suggest doing the same.
Follow these steps to achieve this:
In the browser:
You need to get the subscription object and save it, so your server has access to it: Read more about it
navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
.then(subscription => {
//save subscription.toJSON() object to your server
})});
In the server:
install web-push npm package
And send a web push like this:
const webpush = require('web-push');
setImmediate(async () => {
const params = {
payload: {title: 'Hey', body: 'Hello World'}
};
//this is the subscription object you should get in the browser. This is a demo of how it should look like
const subscription = {"endpoint":"https://android.googleapis.com/gcm/send/deC24xZL8z4:APA91bE9ZWs2KvLdo71NGYvBHGX6ZO4FFIQCppMsZhiTXtM1S2SlAqoOPNxzLlPye4ieL2ulzzSvPue-dGFBszDcFbSkfb_VhleiJgXRA8UwgLn5Z20_77WroZ1LofWQ22g6bpIGmg2JwYAqjeca_gzrZi3XUpcWHfw","expirationTime":null,"keys":{"p256dh":"BG55fZ3zZq7Cd20vVouPXeVic9-3pa7RhcR5g3kRb13MyJyghTY86IO_IToVKdBmk_2kA9znmbqvd0-o8U1FfA3M","auth":"1gNTE1wddcuF3FUPryGTZOA"}};
if (subscription.keys) {
params.userPublicKey = subscription.keys.p256dh;
params.userAuth = subscription.keys.auth;
}
// this key you should take from firebase console for example
// settings -> cloud messaging -> Server key
webpush.setGCMAPIKey('AAAASwYmslc:APfA91bGy3tdKvuq90eOvz4AoUm6uPtbqZktZ9dAnElrlH4gglUiuvereTJJWxz8_dANEQciX9legijnJrxvlapI84bno4icD2D0cdVX3_XBOuW3aWrpoqsoxLDTdth86CjkDD4JhqRzxV7RrDXQZd_sZAOpC6f32nbA');
try {
const r = await webpush.sendNotification(subscription, JSON.stringify(params));
console.log(r);
}
catch (e) {
console.error(e);
}
});

Categories