Conversation
franckc
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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.
I'd recommend moving the DB query within the if (app.get('env') === 'development') statement.
|
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 (authandp256dh) generated whentoJsonis 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
endpointnor theaccountvalues are separately unique in thePushSubscriptiontable. However, no two records should contain both the sameendpointand 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 allPushSubscriptionrecords/: creates a newPushSubscriptionif one does not exist with both a matchingendpointand 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 notificationspsql notifications -c 'CREATE EXTENSION hstore;'npm installnpm install -g sequelize-clisequelize db:migratenode app.jsnode listener/listener.js --continue-file=continue --webhook=http://localhost:3456/eventsService Worker
The service worker is installed in the browser when the DApp is loaded. It allows for the creation of
PushSubscriptions 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
publicdirectoryTo 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
PushSubscriptionis created after this permission is granted. AdditionalPushSubscriptions 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