Giter Site home page Giter Site logo

propel's Introduction

Propel

Build Status Dependency Status devDependency Status

A library to support developers implementing Web Push notifications

Getting Started

To use the Propel library do the following:

  1. Install Propel with npm install --save propel-web-push

  2. Add propel-client.js to your web page

    <script src="/node_modules/propel-web-push/dist/propel-client.js"></script>
  3. Add a web app manifest to your page. It's required by Chrome.

    <link rel="manifest" href="manifest.json">
  4. Use PropelClient in your JavaScript.

    var PropelClient = window.goog.propel.PropelClient;
    
    // Check if push is supported by the current browsers
    if (PropelClient.isSupported()) {
      // Initialise Push Client
      var propelClient = new PropelClient('/sw.js');
      propelClient.addEventListener('statuschange', function(event) {
        if (event.permissionStatus === 'denied') {
          // Disable UI
        } else if (event.currentSubscription) {
          // Enable UI
          // Show that user is subscribed
    
          // Send the subscription object to your server
          fetch('/your-backend-api', {
            method: 'post',
            headers: new Headers().append('Content-Type', 'application/json'),
            body: JSON.stringify(event.currentSubscription)
          });
        } else {
          // Enable UI
          // Show that user is not subscribed
        }
      });
    
      propelClient.subscribe();
      // OR
      propelClient.unsubscribe();
    }
  5. Check out the docs to learn more.

Support

If you’ve found an error in this library, please file an issue: https://github.com/GoogleChrome/Propel/issues

Patches are encouraged, and may be submitted by forking this project and submitting a pull request through GitHub.

License

Copyright 2015 Google, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

propel's People

Contributors

addyosmani avatar jpmedley avatar keyboardsurfer avatar wibblymat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

propel's Issues

Ping endpoint on unsubscribe?

Is there actually any point in pinging the endpoint when the user unsubscribes? Certainly for GCM it doesn't make any difference in practice, because GCM will let the server know if the subscription isn't valid any more and the server needs to be able to handle that anyway.

CC: @gauntface @owencm

Wrong endpoint returned...

I think this may be more of a chrome issue since the underlying service worker (PushManager) is returning the wrong endpoint, but just in case:

  • When running:
var pushClient = new PushClient('/sw.js');
      pushClient.addEventListener('statuschange', function(event) {
    console.log('pushClient:');
    console.log(JSON.stringify(event.currentSubscription));
   }

we get something like:

{"endpoint":"https://android.googleapis.com/gcm/send/[...]","keys":{"p256dh":"BIdjZil_ehfT_MQt95qRFB3qn-55ccb1qYrc-5g0lvWK3XYvF2drD08ulRO_2ljjZVb7vA9R3OydI7QMnHR6C24=","auth":"75pCFtk04rNYL8ozHMEhfQ=="}}

but now if we try to send encrypted data using the new PushAPI specs (i'm using pywebpush) I have to use a different API endpoint:

{"endpoint":"https://gcm-http.googleapis.com/gcm/[...]

The chrome specific webpush library does a quick switcheroo of the URLs but that's cheating ;)

It would probably be useful to mention this in the docs

Feature Set Request

Client:

  • Dedupe push with native app
  • How to distinguish between dismiss and failed subscription attempt
    • This is in reference to a subscribe() call failing due to network error (for example).

Worker:

  • Coalesce Notifications
  • How to manage analytics
  • Notification Icon recommendations
  • How to handle two push events firing simultaneously
    • Can be handled by keeping a reference of the current wait until and ignoring the second push
  • Re-use existing pages rather than opening a new tab on notification click
  • Skip notifications when the page is visible
  • Hide notifications when the page is focused

Server:

  • Setting TTL

Add an event for state changes

I had a rework of the Simple Push demo and thought this might be useful in terms of what an API could look like for the frontend client.

The Push Client Code (i.e. the code abstracted away into a library) is here:

https://github.com/gauntface/simple-push-demo/blob/master/app/scripts/push-client.es6.js

The use of it is here:

https://github.com/gauntface/simple-push-demo/blob/master/app/scripts/main.es6.js

The important parts are:

var pushClient = new PushClient(
  stateChangeListener,
  subscriptionUpdate
);

stateChangeListener is called when the UI for the web page needs to change. subscriptionUpdate is called when a subscription is found and should be sent to the application server.

The state change data is

var stateChangeListener = function(state, data) {
  // console.log(state);
  if (typeof(state.interactive) !== 'undefined') {
    if (state.interactive) {
      pushToggleSwitch.enable();
    } else {
      pushToggleSwitch.disable();
    }
  }

  if (typeof(state.pushEnabled) !== 'undefined') {
    if (state.pushEnabled) {
      pushToggleSwitch.on();
    } else {
      pushToggleSwitch.off();
    }
  }

  switch (state.id) {
  case 'ERROR':
    console.error(data);
    showErrorMessage(
      'Ooops a Problem Occurred',
      data
    );
    break;
  default:
    break;
  }
};

The state contains interactive and pushEnabled variables which indicate where the user should be able to interact with the UI - i.e. don't allow them to attempt to subscribe several times while we want to wait for permissions and pushEnabled is whether the UI should act as thought push has been enabled or not.

This covers a lot of use cases here. The data can be anything relevant and at the moment isn't used here when if should for (for examples errors).

This may change when it comes to enabling encryption however.

Make the tests run on Travis

Now that there are some tests that can be run manually, we should get them running on Travis too.

@gauntface pretty please can you do whatever magic you did for SW Toolbox here?

Debugging failing registrations

@wibblymat @addyosmani @paullewis @petele @owencm @jakearchibald Would love any and all of your thoughts on this:

We've written the pushclient to work as follows:

new PushClient(swReg);
new PushClient('/sw.js');
new PushClient('/sw.js' './my-scope/');

In the example above, what should happen if /sw.js doesn't exist?

In an impending PR we will reject any method that requires use of a sw registration, which isn't an obvious stage for the developer to catch an error that the sw.js file is invalid or 404'ing.

Since the registration is set in the constructor we can't throw an error which means the constructor will always succeed giving the impression everything worked perfectly.

Couple of options to make things clearer to a developer in the case where sw.js doesn't exist or is invalid:

  1. Use a static factory methods that returns a promise with the push client or rejects on error:

    PushClient.createClient('/sw.js')
    .then(pushClient => {
        // All good, set everything up
    })
    .catch(err => {
        // Problem with SW or library
    });
    
  2. Encourage the use of a registration method through samples to check everything is working:

    const pushClient = new PushClient('/sw.js');
    pushClient.getRegistration()
    .then(() => {
        // Everything worked
    })
    .catch(() => {
        // Problem with SW
    });
    
  3. We expect all methods to checked for rejections:

    const pushClient = new PushClient('/sw.js');
    pushClient.addEventListener('statusupdate', event => {
        // Do something with the status update
    });
    pushClient.catch(err => {
        // Something went wrong
    });
    

I'm pro option 1 at the moment.

  • It simplifies the current code base
  • Gives us an opportunity to explain to the user what exactly is wrong at a point in the development flow that makes sense (i.e. as soon as they start using the library)
  • It doesn't block using your own constructor by passing it directly into the constructor

Enable addSubscriptionChangeListener

Having a subscription change listener is nice as I'm not sure all the cases where subscriptions may change and I can keep other parts of my app in sync.

Re-subscribe the device automatically while permission exists

Sometimes subscriptions get deleted. I find it a useful pattern to have my push library always call subscribe() every time it is initialized/included, provided the permission is granted.

Perhaps that should be a default and we should support an initialization paramater to disable it for advanced users that want full predictability to handle cases like that themselves?

Document the features in the README

Currently, the overview in the README is a little thin, just

A library to support developers implementing Web Push notifications

Would be great to mention how the library supports developers.

How exactly do we use propel worker?

The documentation isn't clear on how to actually show notifications. The tests reference

window.goog.propel.worker.helpers.getNotifications()

but that's as far as I got. We can of course modify the service worker sw.js directly but it defeats the purpose of the library I would think...

Propel is leaking in Global Scope

TL;DR Propel is leaking a tonne of variables on the global scope and Mocha wasn't catching it due to the formation of the tests

Mocha has leak detection, but the way it works is it compares the current state before the test to the state after the test.

The problem with this is that the mocha test page loads the script in the html (i.e. before the tests run)

<script src="/dist/client.js"></script>

This means the leaks aren't treated as leaks. The fix for the test is to add a test that asynchronously loads the script and this will trigger the leak detection.

  it('should load window.goog.propel.Client without leaks', done => {
    // By leaks this is referring to the only thing Propel
    // should add to the global scope (i.e. window) is goog
    const scriptElement = document.createElement('script');
    scriptElement.setAttribute('type', 'text/javascript');
    scriptElement.src = '/dist/client.js';
    document.querySelector('head').appendChild(scriptElement)
    .onload = () => {
      console.log('Script Loaded');
      done();
    };
  });

Add requestPermission to the client

I like conceptually breaking subscribing away from requesting permission, because:

a) The best way of requesting permission (i.e. via the Notifications API) doesn't require a network round trip whereas calling subscribe would
b) Conceptually I think it's clear to think of these as separate actions, one that just must follow the other. It seems surprising initially that to request permission you need to subscribe the device

Be careful about JSON.stringify for PushSubscription objects

Learned something interesting today we need to handle:

  • In Chrome 43 through Chrome 45 JSON.stringify returns an empty object for PushSubscription objects. The recomended workaround is to reconstruct the object before stringifying, i.e. const fixedSub = { endpoint: sub.endpoint, registrationId: sub.registrationId }
  • In Chrome 51+ you cannot directly access the keys property on PushSubscription objects but need to JSON.stringify first

Hence I think the best thing to do is something like

let pushSub = await getPushSubscription();
pushSub = Object.assign({ endpoint: pushSub.endpoint, registrationId: pushSub.registrationId }, JSON.parse(JSON.stringify(pushSub)));

I haven't tried it but so far as I can see that should always ensure we get the keys we want working around the bugs and weirdnesses of other versions?

Worth noting that with this solution registrationId will always be a key but will be undefined from the version of Chrome which deprecated it (46 or so?). I suspect the better solution is to deprecate registrationId with respect to this library and thus just remove the extraction of that key from the above.

Document supported browser versions

It would be useful if the README or similar documented the browsers that are supported (including versions), as well as providing some idea of the library's current status and intended use case (alpha, beta, stable, etc.).

Service Worker Registration

cc @jeffposnick @wibblymat @paullewis @petele

Me and Pete were discussion how to use the Propel library and one of the things that came up was how to use it with your own SW.

This isn't obvious and passing in a worker url and scope for something you've registered already for different reasons feels a bit weird to give the url and scope for.

The API at present is:

new Client({workerUrl: '/sw.js', scope: './'});

Would anyone object to changing this to something a bit more permanent of:

new Client('/sw.js'); // Scope is predefined as current script path + /.goog.propel.push

new Client('./sw.js', './');

new Client(swRegistration); 

Service work url can be obtained from the installing, waiting or active service worker object and the scope can be retrieved from the registration object.

Is there any downsides / edge cases to this?

The only reason I'm proposing ditching the options object is the worker.js file is currently empty and I'm not convinced shipping a default service worker is a good idea here, shipping with a library to help generate notifications makes sense, but it will still require some work on the developers behalf and expecting worker.js to exist in the same place as the client.js file feels a bit risky / unpredictable. The above at least forces some decision to be made.

What do people think? This is new and is likely to come up again with any libraries around background sync.

Increase Visibility Time Of Web Push Notification

Hi

As of now the default time of visibility for my push message on chrome browser is 19 seconds which is too low, But Other push services like OneSignal, Pushcrew, Let Reach are having visibility time of upto 3 minutes for there messages. How can i do that, Need some help with this please

Thank You

Async Loading + Usage of Propel

Please close this bug if we don't expect developers to load the propel library as a separate async script.

In most third party libraries I've seen the script URL take an extra parameter for a method to call on the window OR it just has a specific callback on the window object that it'll look for and call if it exists, not sure if there is a better option than this.

Would it be worth adding something along these lines to Propel?

Group tests to Notification Permission Types?

Firefox is awesome in that you can dynamically (from within a page) set permission for notifications, making it easy to run the test suite as is and set the permissions.

In Chrome we can set these permissions via the user preferences which can't be set from within the page directly, so we either need to split the tests into groups based on permissions or find a way for the test runner to call out into the node code running the tests and set the preferences dynamically (If possible).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.