-
Notifications
You must be signed in to change notification settings - Fork 196
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome. A few comments inline. Let's discuss to resolve them...
res.sendStatus(201) | ||
}) | ||
|
||
app.post('/events', async (req, res) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to add authentication otherwise user could get spammed by an external service calling this webhook.
We could have a secret that the listener passes as a header value in the request and that the notifier service checks. Another possibility would be to rely on internal queues rather than webhooks: listener would enqueue events and notifier would dequeue and process. Let's discuss...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to be exposed to the outside world right? The simple solution here is I don't expose this endpoint outside the Kubernetes cluster. The listener can just call it from inside the cluster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, @tomlinton 👌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent idea @tomlinton !
origin-notifications/app.js
Outdated
try { | ||
// should safeguard against duplicates | ||
await webpush.sendNotification(s, JSON.stringify({ | ||
title: log.eventName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll probably want to localize this string. Add a TODO
}).forEach(async s => { | ||
try { | ||
// should safeguard against duplicates | ||
await webpush.sendNotification(s, JSON.stringify({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll probably want to have some retry in case for some reason that server is temporarily down.
Ok to just add a TODO for now.
Note: there is a retry function implemented in origin-js/src/utils/retries.js
We need this in quite a few places so perhaps we should just pull it out of origin-js and put it in a util package or equivalent ?
origin-notifications/migrations/20181019004743-create-push-subscription.js
Show resolved
Hide resolved
@@ -0,0 +1,9 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a TODO. For productionizing this we'll need to use env variables (DATA_USERNAME, DATABASE_PASSWORD, etc...)
up: (queryInterface, Sequelize) => { | ||
return queryInterface.createTable('PushSubscriptions', { | ||
id: { | ||
allowNull: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unecessary since this is a primary key and therefore it can't be null.
Also when we decide this is no longer a POC but a real feature, let's add some unit tests... :) |
5250352
to
fb5e1f4
Compare
Based on my conversation with @franckc this morning, here are some additional considerations:
Before merging this PR, I will add a compound index for the endpoints and accounts along with rate limiting for subscription creation. The Boss will add add a shared secret to this new server and the discovery server. |
origin-notifications/app.js
Outdated
@@ -50,8 +50,13 @@ app.use(bodyParser.json()) | |||
// currently showing all subscriptions in an unauthenticated index view - maybe not a good idea in production? | |||
app.get('/', async (req, res) => { | |||
const subs = await PushSubscription.findAll() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend moving the DB query within the if (app.get('env') === 'development') statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have been notified by my supervisor that I should approve this request prompto.
This piece of software engineering looks very solid to me. I dutifully approve merging it into the Origin Protocol code repository.
Faithfully,
FC
Vapid keygen, for reference: const webpush = require('web-push');
const vapidKeys = webpush.generateVAPIDKeys()
vapidKeys.publicKey
// gives 'BDO0P...eoH'
vapidKeys.privateKey
// gives 'sjfiejfwief_wefwfwe' |
Checklist:
Format code to be consistent with the projectUpdate any relevant READMEs and docsDescription:
This is a proof-of-concept for implementing browser-based push notifications. Here is an overview of what is included and what may or may not need to be handled in the near/distant future:
Notifications Server
A new subdirectory (origin-notifications) is created. It contains an Express server with a PostgreSQL connection mapped using Sequelize. There is only one model, the
PushSubscription
, which includes the following attributes:id
: default, automatically-incremented integercreatedAt
: default, automatically-generated timestampupdatedAt
: default, automatically-generated timestampendpoint
: URL string generated when a subscription is created in the browser, determined by the push service provider (Google FCM or Mozilla), with a unique token appended to the pathkeys
: a pair of values (auth
andp256dh
) generated whentoJson
is called on a subscriptionexpirationTime
: currently left blankaccount
: the subscriber's then-current ETH addressSince the subscription object is unique to the client device, it may be relevant to more than one ETH account. For this reason, neither the
endpoint
nor theaccount
values are separately unique in thePushSubscription
table. However, no two records should contain both the sameendpoint
and the sameaccount
.The only events currently resulting in notifications are those involving an offer. New messages and listings could be candidates for future notifications.
Three environment variables are necessary. An email address should be provided in case the push service provider needs to contact the sender of the push events. And a VAPID key pair can be generated by the web-push library.
Routes
/
: returns allPushSubscription
records/
: creates a newPushSubscription
if one does not exist with both a matchingendpoint
and a matchingaccount
/events
: conditionally converts a blockchain event into a push notification, which is sent to any subscription endpoints generated by an ETH account matching the buyer or sellerTo Consider
/
To Test
createdb notifications
psql notifications -c 'CREATE EXTENSION hstore;'
npm install
npm install -g sequelize-cli
sequelize db:migrate
node app.js
node listener/listener.js --continue-file=continue --webhook=http://localhost:3456/events
Service Worker
The service worker is installed in the browser when the DApp is loaded. It allows for the creation of
PushSubscription
s and handles push events from the server and user interactions with notifications. The code runs in a separate thread outside of the DOM context and is usually executed when the DApp is not currently open in the browser. It is scoped to the origin, and thus, registrations and notifications will be specific to the dev/staging/prod environment as long as we continue to use separate subdomains.To Consider
public
directoryTo Test
DApp Changes
The most critical UX precaution is to ask the user to enable notifications at the most compelling time before actually initiating the browser's native permission request prompt. Once a user denies permission, she is unlikely to ever grant it since doing so would require changing the browser's settings. This first implementation prompts the user to enable notifications immediately after creating a listing or making an offer. If the modal is dismissed, a stronger prompt is rendered explaining the importance of push notifications in a decentralized application. Permission is only requested at the API level after the user affirms her interest in enabling notifications. A
PushSubscription
is created after this permission is granted. AdditionalPushSubscription
s are created each time the user creates a listing or makes an offer using a different ETH account using a browser in which permission was already granted.Two environment variables are required: the public VAPID key and the notification server's URL.
To Consider
To Test