Last active
April 15, 2017 19:23
-
-
Save meulta/8feb9ae9bc4e7a448361393a45c28f44 to your computer and use it in GitHub Desktop.
Push notifications
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const webPush = require('web-push'); | |
const restify = require('restify'); | |
const builder = require('botbuilder'); | |
const fs = require('fs'); | |
const vapidKeyFilePath = "./vapidKey.json"; | |
var vapidKeys = {}; | |
if (fs.existsSync(vapidKeyFilePath)) { | |
//if the vapid file exists, then we try to parse its content | |
//to retrieve the public and private key | |
//more tests might be necessary here | |
try { | |
vapidKeys = JSON.parse(fs.readFileSync(vapidKeyFilePath)); | |
} | |
catch (e) { | |
console.error("There is an error with the vapid key file. Log: " + e.message); | |
process.exit(-1); | |
} | |
} | |
else { | |
//if the file did not exists, we use the web-push module to create keys | |
//and store them in the file for future use | |
//you should copy the public key in the index.js file | |
vapidKeys = webPush.generateVAPIDKeys(); | |
fs.writeFileSync(vapidKeyFilePath, JSON.stringify(vapidKeys)); | |
console.log("No vapid key file found. One was generated. Here is the public key: " + vapidKeys.publicKey); | |
} | |
//here we setup the web-push module for it to be able to | |
//send push notification using our public and private keys | |
webPush.setVapidDetails( | |
'mailto:example@yourdomain.org', | |
vapidKeys.publicKey, | |
vapidKeys.privateKey); | |
//standard way of creating a rest API for a bot | |
var server = restify.createServer(); | |
server.use(restify.bodyParser()); | |
server.listen(process.env.port || process.env.PORT || 3000, function () { | |
console.log('%s listening to %s', server.name, server.url); | |
}); | |
// Create chat bot | |
var connector = new builder.ChatConnector({ | |
appId: process.env.MICROSOFT_APP_ID, | |
appPassword: process.env.MICROSOFT_APP_PASSWORD | |
}); | |
var bot = new builder.UniversalBot(connector); | |
server.post('/api/messages', connector.listen()); | |
//========================================================= | |
// Bots Dialogs | |
//========================================================= | |
//this is a 'dictionary' in which we store every push subscription per user | |
var pushPerUser = []; | |
//this event handler is looking for every message sent by the bot | |
//we look into the dictionary to see if we have push subscription info | |
//for this user and we send a push notif with the bot message | |
//this will keep send the message to the user through the channel | |
bot.on("outgoing", function (message) { | |
if (pushPerUser && pushPerUser[message.address.user.id]) { | |
var pushsub = pushPerUser[message.address.user.id]; | |
webPush.sendNotification({ | |
endpoint: pushsub.endpoint, | |
TTL: "1", | |
keys: { | |
p256dh: pushsub.key, | |
auth: pushsub.authSecret | |
} | |
}, message.text); | |
} | |
}); | |
//here we handle incoming "events" looking for the one which might come from | |
//the backchannel. we add or replace the subscription info for this specific user | |
bot.on("event", function (message) { | |
if (message.name === "pushsubscriptionadded") { | |
pushPerUser[message.user.id] = message.value; | |
} | |
}); | |
//here is a classic way of greeting a new user and explaining how things work | |
bot.on('conversationUpdate', function (message) { | |
if (message.membersAdded) { | |
message.membersAdded.forEach(function (identity) { | |
if (identity.id === message.address.bot.id) { | |
var reply = new builder.Message() | |
.address(message.address) | |
.text("Howdy! I am a [demo bot](https://github.com/meulta/webchat-pushnotifications) using the [WebChat control](https://github.com/Microsoft/BotFramework-WebChat) and with Push Notifications! Say **hello** and I will send a message every 10 seconds. If you accepted notifications, you will get one! If you close the tab but leave the browser opened, you will get a notification when I talk. Oh and say **stop** to shut me off :)"); | |
bot.send(reply); | |
} | |
}); | |
} | |
}); | |
//this part is a very simple way of simulating proactive dialogs | |
//we only send a message with a integer that we increment | |
//every 5 seconds. this is for push notifications test purposes and should not | |
//be use as base to create any real life bot :) | |
var loop = false; | |
var count = 1; | |
bot.dialog('/', function (session) { | |
if (session.message.text === "stop") { | |
session.send("Stopping loop"); | |
loop = false; | |
} | |
else if (!loop) { | |
loop = true; | |
count = 1 | |
proactiveEmulation(session); | |
} | |
}); | |
var proactiveEmulation = (session) => { | |
if (loop) { | |
session.send(`Hello World of web push! :) (${count++})`); | |
setTimeout(() => proactiveEmulation(session), 5000); | |
} | |
}; | |
//we use this to serve the webchat webpage | |
server.get(/\/web\/?.*/, restify.serveStatic({ | |
directory: __dirname | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
const DIRECTLINE_SECRET = ""; //you get that from the direct line channel at dev.botframework.com | |
const VAPID_PUBLICKEY = ""; //you get that from the server, which will generate a vapidKey.json file | |
var startChat = function () { | |
let botConnection; | |
if (getParameterByName("isback") === 'y') { | |
//if we are resuming an existing conversation, we get back the conversationid from LocalStorage | |
botConnection = new DirectLine.DirectLine({ | |
secret: DIRECTLINE_SECRET, | |
conversationId: localStorage.getItem("pushsample.botConnection.conversationId"), | |
webSocket: false | |
}); | |
} else { | |
//if it is a brand new conversation, we create a fresh one | |
botConnection = new DirectLine.DirectLine({ | |
secret: DIRECTLINE_SECRET, | |
webSocket: false | |
}); | |
} | |
botConnection.connectionStatus$ | |
.filter(s => s === 2) //when the status is 'connected' (2) | |
.subscribe(c => { | |
//everything is setup in DirectLine, we can create the Chatbot control | |
BotChat.App({ | |
botConnection: botConnection, | |
user: { id: botConnection.conversationId}, //you could define you own userid here | |
resize: 'detect' | |
}, document.getElementById("bot")); | |
//we setup push notifications (including service worker registration) | |
setupPush((subscriptionInfo) => { | |
//once push notifications are setup, we get the subscription info back in this callback | |
//we use the backchannel to send this info back to the bot using an 'event' activity | |
botConnection | |
.postActivity({ | |
type: "event", | |
name: "pushsubscriptionadded", | |
value: subscriptionInfo, | |
from: { id: botConnection.conversationId } //you could define your own userId here | |
}) | |
.subscribe(id => { | |
//we store the conversation id which we get back from postActivity(...) in the LocalStorage | |
//we will need this in case of conversation resuming | |
localStorage.setItem("pushsample.botConnection.conversationId", botConnection.conversationId); | |
}); | |
}); | |
}); | |
botConnection.activity$.subscribe(c => { | |
//here is were you can get each activity's watermark | |
//we do not do anything in this sample, but you can use it if you need | |
//to restore history at resuming at a specific moment in the conversation | |
console.log(botConnection.watermark); | |
}); | |
}; | |
// Push | |
var setupPush = function (done) { | |
//first step is registering the service worker file | |
navigator.serviceWorker.register('service-worker.js') | |
.then(function (registration) { | |
//once the sw is registered, we try to get an existing push subscription | |
return registration.pushManager.getSubscription() | |
.then(function (subscription) { | |
//if the subscription exists, then we pass is to the next chained .then function using return | |
if (subscription) { | |
return subscription; | |
} | |
//if the subscription does not exists, we wrap the VAPID public key and create a new one | |
//we pass this new once to the next chaind .then function using return | |
const convertedVapidKey = urlBase64ToUint8Array(VAPID_PUBLICKEY); | |
return registration.pushManager.subscribe({ | |
userVisibleOnly: true, | |
applicationServerKey: convertedVapidKey | |
}); | |
}); | |
}) | |
.then(function (subscription) { | |
//wrapping the key and secret | |
const rawKey = subscription.getKey ? subscription.getKey('p256dh') : ''; | |
const key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : ''; | |
const rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : ''; | |
const authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : ''; | |
const endpoint = subscription.endpoint; | |
//we call back the code that asked to register push notification with the subscription information | |
done({ | |
endpoint: subscription.endpoint, | |
key: key, | |
authSecret: authSecret | |
}); | |
}); | |
} | |
// Helpers | |
function getParameterByName(name, url) { | |
if (!url) { | |
url = window.location.href; | |
} | |
name = name.replace(/[\[\]]/g, "\\$&"); | |
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), | |
results = regex.exec(url); | |
if (!results) return null; | |
if (!results[2]) return ''; | |
return decodeURIComponent(results[2].replace(/\+/g, " ")); | |
} | |
function urlBase64ToUint8Array(base64String) { | |
const padding = '='.repeat((4 - base64String.length % 4) % 4); | |
const base64 = (base64String + padding) | |
.replace(/\-/g, '+') | |
.replace(/_/g, '/'); | |
const rawData = window.atob(base64); | |
const outputArray = new Uint8Array(rawData.length); | |
for (let i = 0; i < rawData.length; ++i) { | |
outputArray[i] = rawData.charCodeAt(i); | |
} | |
return outputArray; | |
} | |
//everything is defined, let's start the chat | |
startChat(); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var baseurl = ""; //replace that with you website baseurl. you could handle this differently but it was simplier in this sample | |
self.addEventListener('push', function (event) { | |
//creating the notification message (we should never be in the "no message" case) | |
var payload = event.data ? event.data.text() : 'No message...'; | |
//we show a notification to the user with the text message | |
//and an icon which is hosted as a resource on the website | |
event.waitUntil( | |
self.registration.showNotification('Chat bot!', { | |
body: payload, | |
icon: '/web/img/thinking_morphi.png' | |
}) | |
); | |
}); | |
self.addEventListener('notificationclick', function (event) { | |
// Android doesn't close the notification when you click on it | |
// See: http://crbug.com/463146 | |
event.notification.close(); | |
// This looks to see if the current is already open and | |
// focuses if it is | |
event.waitUntil( | |
//searching for all clients / tab opened in the browser | |
clients.matchAll({ | |
type: "window" | |
}) | |
.then(function (clientList) { | |
//going through the list of clients/tab and trying to find our website | |
for (var i = 0; i < clientList.length; i++) { | |
var client = clientList[i]; | |
//if we find it, we put focus back on the tab | |
if ((client.url.toLowerCase() == baseurl + '/web/index.html' || client.url.toLowerCase() == baseurl + '/web/index.html?isback=y') && 'focus' in client) | |
return client.focus(); | |
} | |
if (clients.openWindow) { | |
//if we did not find it, then we re-open it with the isback=y parameter | |
//to ensure that we resume the conversation using the conversationid | |
return clients.openWindow('/web/index.html?isback=y'); | |
} | |
}) | |
); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment