Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add firebase cloud messaging and browser push #491

Merged
merged 27 commits into from
Apr 20, 2017
Merged

Add firebase cloud messaging and browser push #491

merged 27 commits into from
Apr 20, 2017

Conversation

nicksellen
Copy link
Contributor

Initial work only.

Currently only pushes messages when you register for push.

Need to have firebase.json in project root with service account credentials from FCM. Will move to config somewhere in future.

No tests yet...

@nicksellen nicksellen mentioned this pull request Apr 10, 2017
3 tasks
@nicksellen
Copy link
Contributor Author

nicksellen commented Apr 10, 2017

I changed the plan a bit from #394 -

There are two concepts:

  1. does the user want push notifications enabled (intention)
  2. are push notifications enabled (reality)

I realised just storing the tokens in the database does not reflect intention properly. To support removing all registrations in one go it would need to keep the registration but mark it for removal, so when that browser connects again it can uninstall itself. This seems complicated and not especially useful.

Part of the complexity is that you cannot assume the registration tokens will stay the same forever, and the browser can always get one. If intention were stored in the database the browser would need an identifier so it can know which registration is intended for it (or maybe add something to the session). But again, getting complicated and little benefit right now.

I changed it so you so that now you and enable/disable push for the current browser you are using, and nothing else. The intention is stored in the browser (in a cookie), and the client app does it's best to make it so. I think this fits well to how mobile works too.

So UX is nice and simple and looks like:

pushconfig

Thoughts?

And some tasks from the issue:

  • get feedback on UX
  • I would like to only load the firebase js libs if they are needed
  • I would like to fully remove the service worker when you turn off notifications, fcm doesn't seem designed to make this easy though, seems possible though, but maybe a bit more manual work
  • some code tidy/rejig
  • plug it into the job thing to send notifications when needed
  • notification actions when you click the message
  • check browser supports enough things first
  • get the fcm sender id config into the service worker
  • load the fcm server config from somewhere better
  • ... tests
  • unregister on logout
  • get feedback on the message content/style
  • get someone else to try it out
  • try it manually in some other browsers
  • use browserstack... automated or manual?
  • add a key to manifest.json Add manifest.json and base service worker #493

@simison
Copy link
Contributor

simison commented Apr 10, 2017

plug it into the job thing to send notifications when needed

This would be at message-unread.server.job.js for unread notifications. I don't think we want to complicate this with any other kind of notifications just yet.

So UX is nice and simple and looks like
...
get feedback on UX

I like it!

We will probably want to nag users who don't have it checked by asking them if they want to enable it, store that we've nagged them to the browser session and ask again only when they login with new browser. But that can be the next step once this PR is done.

load the fcm server config from somewhere better

You can just add an example config block to config/env/default.js and the real one to your config/env/local.js (and perhaps to config/env/local.sample.js)

I would like to only load the firebase js libs if they are needed (only ~11kb though... in dev even)

Another feature for another PR. :-)

notification actions when you click the message

Link to the message thread. Just add analytics tags to the URL, see an example.

@nicksellen
Copy link
Contributor Author

We will probably want to nag users

Yup, getting it working and live first seems more useful, make sure it actually works... I think it's pretty easy to get too naggy and annoying with push.

I suspect it will be better to notify on every new message too. I believe you can tag the messages too such that newer ones replace older ones so they don't stack up.

@nicksellen
Copy link
Contributor Author

Aha, my fight with travis was victorious! 🥇

@simison
Copy link
Contributor

simison commented Apr 12, 2017

I suspect it will be better to notify on every new message too. I believe you can tag the messages too such that newer ones replace older ones so they don't stack up.

Yep.

What's up with the change to Firefox?

PR looks great!

@nicksellen
Copy link
Contributor Author

nicksellen commented Apr 12, 2017

PhantomJS doesn't have much of the newer browser stuff supported and I'd like to add tests for the browser notifications.

Although using PhantomJS locally (same version too, 2.1.1 iirc) doesn't fail in the tests (isSupported gets set to false) but in travis it failed inside the firebase js lib because Promise doesn't exist.

Would be good to check it more properly with an unsupported browser to make sure it works fine.

@simison
Copy link
Contributor

simison commented Apr 12, 2017

Would be good to check it more properly with an unsupported browser to make sure it works fine.

We have a BrowserStack account you could use to test old browsers. @chmac could share it with you via LastPass. (@chmac could you also re-share it with me and tick "can modify the password"?)

@chmac
Copy link
Member

chmac commented Apr 12, 2017

@simison Don't you have your own account? Anyway, I've just shared the login with you.

@nicksellen If you send me your lastpass email I can share it with you.

@simison
Copy link
Contributor

simison commented Apr 12, 2017 via email

@nicksellen
Copy link
Contributor Author

@nicksellen
Copy link
Contributor Author

nicksellen commented Apr 12, 2017

There are nice tests now :) It was quite a battle to get karma/etc to be happy, so easy to lose track of a promise somewhere...

I split the client service into firebaseMessaging and push, so I can test the push service without loading any firebase libs. I'm quite pleased with the simplicity of the promise-based stuff [1].

[1]

@nicksellen
Copy link
Contributor Author

F*** you Travis!

@nicksellen
Copy link
Contributor Author

Ready for review!

Just a few things to look at #491 (comment)

@nicksellen nicksellen changed the title WIP: Add firebase cloud messaging and browser push Add firebase cloud messaging and browser push Apr 12, 2017
firebase.token = token;

// .. and enable it to be initialized
firebaseMessaging.shouldInitialize = true;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set this to false in a beforeEach hook


// Attempt to initialize Facebook SDK on first page load
// If this fails, we'll try this again on successfull login
Facebook.init();

push.init();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A short comment over this could do. :-)

@@ -22,6 +22,9 @@ module.exports = function(app) {
// Return a 404 for all undefined api, module or lib routes
app.route('/:url(api|modules|lib|developers)/*').get(core.renderNotFound);

// Gives the service worker access to any config it needs
app.route('/config/sw.js').get(core.renderServiceWorkerConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally these been exposed to index html template from config/lib/express.js.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service worker does not have access to the HTML/DOM. It's very restricted.

Perhaps a better option is sending a message to it once it's started up [1]. I'm not very happy with the current approach.

[1] http://craig-russell.co.uk/2016/01/29/service-worker-messaging.html

exports.renderServiceWorkerConfig = function(req, res) {
res.set('Content-Type', 'text/javascript')
.send('var FCM_SENDER_ID = ' + JSON.stringify(config.fcm.senderId) + ';\n');
};
Copy link
Contributor

@simison simison Apr 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExpressJS has support for jsonp, but normally configs have been exposed to index html template from config/lib/express.js.


messaging.setBackgroundMessageHandler(function(payload) {
// not actually used, but without it here firefox does not receive messages...
console.log('received payload', payload);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function never gets fired but it has to be there? messaging.setBackgroundMessageHandler(Function.prototype) might be handy.

Also if Angular is already in window at this point, angular.noop is available.

Copy link
Contributor Author

@nicksellen nicksellen Apr 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No angular here, the service worker is a standalone script and they run with a limited environment.

firebase/quickstart-js#71 has a discussion about it the background handler stuff - the gist is that by sending a notification payload (as opposed to data) you are specifying it should be a regular notification, and not be overridable.

my suspicion about why it didn't work without that in firefox is that without defining some kind of listener it stops listening for new messages. it would be useful to verify it properly though...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okidoki, thanks for clarifying!


importScripts('/lib/firebase/firebase-app.js');
importScripts('/lib/firebase/firebase-messaging.js');
importScripts('/config/sw.js');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, now I understood why the config was loaded like it was earlier! Ignore my earlier comments about this.

We should probably move all the configs to load like this. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I liked them in index.html. This way would be 1 extra http request too, but could load the config into a standard TrustrootsConfig object or something. I'm easy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're handy in index.html indeed but stops making the app work self hosted and offline (e.g. on mobile). It's another discussion and another PR anyway.

package.json Outdated
@@ -140,6 +141,7 @@
"nodemailer-stub-transport": "~1.1.0",
"phantomjs-prebuilt": ">=2.1",
"promise": "~7.1.1",
"proxyquire": "^1.7.11",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~ just for consistency.

package.json Outdated
@@ -61,6 +61,7 @@
"express-paginate": "~0.2.1",
"express-session": "~1.14.0",
"fbgraph": "~1.3.0",
"firebase-admin": "^4.2.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~ just for consistency.

pushService.sendUserNotification(user, {
title: 'Trustroots',
body: 'A ' + platform + ' push device was added!',
click_action: 'http://localhost:3000/profile/edit/account'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might work here better for flexibility with different setups (Docker IP, https):

var protocol = (config.https === true || req.protocol === 'https') ? 'https' : 'http';
var host = protocol + '://' + req.get('host'); // http://localhost:3000

...or actually this might be already available as res.locals.hostPort

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, I forgot to fix that bit.

I also wonder whether it would be a good idea to send a notification at that point in production too, but only to the device you just added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, maybe in development some other way to send notifications could be handy for manual testing/playing purposes...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also wonder whether it would be a good idea to send a notification at that point in production too, but only to the device you just added?

Yeah I think so, it kinda confirms to the user that it worked.

So to send push to all the devices, just remove that if? I think it's okay to send it to all the devices even (how many there can be), just to avoid extra work. Feels kind of a security feature even in that way anyway. :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy could be something like "You just enabled Trustroots platform push notifications. Yay!"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, sounds good :)

</div>

<p class="help-block" ng-if="profileEditAccount.push.isSupported"><small>This will send notifications even when the website is closed.</small></p>
<p class="help-block" ng-if="!profileEditAccount.push.isSupported"><small>Your browser does not support push notifications.</small></p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When unsupported, the whole block could just be hidden?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it that way so it communicates: if you use another browser then you can get push notifications

It also helps to keep a consistent interface, so users don't get confused wondering where the option went when they had it available on another browser.

I don't feel that strongly about it though. Happy to hide it all if unsupported if you prefer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thinking 👍, let's leave it visible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The section could go above password tho, so it would be more noticeable.

image

@simison
Copy link
Contributor

simison commented Apr 13, 2017

use browserstack... automated or manual?

There's an old script for automated testing: https://github.com/Trustroots/trustroots/tree/master/scripts/selenium

...but frankly I'm not too keen to keep that up to date. I think manual testing and those few unit tests are great, we should notice pretty quick if it fails, esp. if we push some events to Influx stats.

bower.json Outdated
@@ -4,6 +4,7 @@
"dependencies": {
"angular": "1.5.8",
"angular-animate": "1.5.8",
"angular-cookies": "1.5.8",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW there's already https://github.com/tymondesigns/angular-locker which might save you some kb's since no need to add this lib, but I'm fine either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a good idea

source: 'push-notification',
medium: 'fcm',
campaign: 'device-added',
content: 'reply-to'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this should be?

Copy link
Contributor

@simison simison Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium could probably be just push-notification, I'm not sure if we gonna differentiate different platforms in the future but doesn't matter for now.

Since there is just one place (notification click) pointing to the same url, content can probably be left empty. It's used to make difference e.g. between different parts of email linking to the same URL (e.g. "footer" and "header"). ...I think.

They're explained in this doc and at query builder

No stress over these, we can adjust them later.

@nicksellen
Copy link
Contributor Author

Hmm a weird error from a simple change:

dst is undefined in public/lib/angular/angular.js

Investigating...

@nicksellen
Copy link
Contributor Author

Aha, SettingsService is better than SettingsFactory...

@nicksellen
Copy link
Contributor Author

@simison not sure why the pr build fails so frequently when the push one seems fine? I don't think there is an actual error.

One thing I haven't done is tried it in other browsers, but this is not totally simple because the security stuff requires https when not using localhost, so I can't access it from my phone via my machines ip address.

One solution would be to setup https locally, but also need to change a few settings to use DOMAIN env value instead of localhost (well, maybe just the csp dev config). I don't have time for this right now, and would suggest just trying it out when properly deployed.

@nicksellen
Copy link
Contributor Author

Perhaps someone at least has a windows machine or mac to try it on though?

@simison
Copy link
Contributor

simison commented Apr 19, 2017

I'll build this to dev machine, just a mo.

Edit: done!

@simison
Copy link
Contributor

simison commented Apr 19, 2017

not sure why the pr build fails so frequently when the push one seems fine? I don't think there is an actual error.

Switching to Firefox is probably "causing" frontend Karma tests to fail now, but I'm not sure why exactly. Might be just some done():s missing since it's just timeouting.

@nicksellen
Copy link
Contributor Author

Tried it on dev.trustroots.org on desktop and mobile, it all works!

Chrome

Chrome did not show the notification when the browser tab was the active one on the screen, but did when the tab was closed, and when the browser was not in the foreground. Not actually sure about if the browser is completely closed (I guess not).

When loading the site again after enable notifications it asked me if I wanted to add it to the home screen (so I suspect it only gets used once a web worker has been installed).

The notification icon is the chrome icon.

Firefox

Firefox always showed the notification (well, didn't try with browser completely closed, I guess not).

The notification icon is the firefox icon.

Android browser (not supported)

@simison
Copy link
Contributor

simison commented Apr 20, 2017

Great!

So my todo seems to be:

  • look into those failing Karma test if I could help it out somehow
  • merge
  • get FCM account and config for Trustroots
  • deploy live

My goal is to get this live some time Thu—Sat.

Firebase is more abstracted now so should cause less problems not
having all the latest browser APIs.
@nicksellen
Copy link
Contributor Author

nicksellen commented Apr 20, 2017

Switching to Firefox is probably "causing" frontend Karma tests to fail now, but I'm not sure why exactly. Might be just some done():s missing since it's just timeouting.

I do not feel motivated to try and fix it for this issue. I think it could take some time and is not really related to the push features. Actually it might be possible to switch to back to PhantomJS as I have abstracted the firebase stuff further away now - just pushed a commit to revert it (although I think it will have to change again eventually now the maintainer stepped down).

@simison
Copy link
Contributor

simison commented Apr 20, 2017

Switching to Firefox is probably "causing" frontend Karma tests to fail now, but I'm not sure why exactly. Might be just some done():s missing since it's just timeouting.

I do not feel motivated to try and fix it for this issue. I think it could take some time and is not really related to the push features. Actually it might be possible to switch to back to PhantomJS as I have abstracted the firebase stuff further away now - just pushed a commit to revert it back (although I think it will have to change again eventually now the maintained stepped down).

I just checked, our Karma is 1.3.0 and newest is 1.6.0 so that might be causing it as well. Updating Karma + switching to Firefox seems like a separate, not so urgent PR.

@simison simison merged commit 78596c1 into master Apr 20, 2017
@simison simison deleted the fcm branch April 20, 2017 14:03
simison added a commit that referenced this pull request Jun 3, 2017
Related to #491 "Add firebase cloud messaging and browser push"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants