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

Push notifications #7

Open
elfeffe opened this Issue Nov 4, 2016 · 10 comments

Comments

Projects
None yet
5 participants
@elfeffe

elfeffe commented Nov 4, 2016

Great project!
What about push notifications?

@gliechtenstein

This comment has been minimized.

Contributor

gliechtenstein commented Nov 5, 2016

Great question. Before I start explaining, please correct me if I'm wrong. I think I have enough experience with push notifications (Jasonette started out as something extracted out from a messaging app.

As far as I know, push notification in most cases is a server-side thing. Let me use a messaging app as an example:

  1. John signs up to the app. After the sign up is complete, the app sends the phone's device token to the app's server.
  2. The server stores the device token under John's account for later reference.
  3. Jane signs up to the same app. Jane finds John and sends a message.
  4. When Jane submits the message, it sends a POST request to the server.
  5. The server looks for John in the DB and creates a chat message in the DB.
  6. Then, the server looks up John's device token.
  7. The push notification on iPhones need to go through Apple's push server (APNS), so the app's server makes a POST request to APNS with John's device token.
  8. APNS delivers the push to the phone associated with the device token, which is John's phone.

Of course, there is a way to bypass this. For example if Jane's phone knows exactly what John's device token is, then it can directly jump to step 7 and send a push to that device token through APNS immediately. But in most cases this is not realistic since most push based apps need some sort of centralized way of storing and managing device tokens for their users.

That said, there are many ways to tackle this problem.

  • Option 1. Jasonette doesn't implement push. Users implement it on their own by customizing the objective-c code
  • Option 2. Jasonette implements an action that can take care of the step 1 above, so you can send a device_token to the backend just by expressing it in JSON. Everything after that should be taken care of on YOUR server. If you already have a server this is not much of a problem and you can probably implement it in 30 minutes using some library.
  • Option 3. Jasonette becomes a client/server integrated solution, you as a user sign up and send a push notification through a hypothetical "JASON push server". This will be much simpler but you still need to upload push certificates to the JASON push server. To give some concrete examples, this "JASON push server" would look something like https://azure.microsoft.com/en-us/services/notification-hubs/ or https://www.urbanairship.com/

The project is not yet at a stage where I want to make a decision on whether I will build a dedicated push broker service (Option 3) or not. And honestly I think building Jasonette into something that's tightly coupled with a "special server" may limit its potential.

I think option 2 feels most rational at this point, since you still have a way to send device tokens to your server without touching any native code, but you're not tied to a specific push provider.

But again, let me know if you know a better solution. Otherwise I will soon start working on the 'option 2' and post an update here :)

@seletz

This comment has been minimized.

Contributor

seletz commented Nov 5, 2016

An action for sending tokens and new system event which would be fired when a push message arrives would be cool.

I fully agree that writing yet another push service backend would bind jasonette too tightly to one implementation.

OTOH I see that both UA and MS Azure require hooking their own frameworks on the client (iOS) side. I think makes it very difficult to come up with a generic solution for a system event.

@gliechtenstein

This comment has been minimized.

Contributor

gliechtenstein commented Nov 6, 2016

Here's the push notification branch: https://github.com/Jasonette/JASONETTE-iOS/compare/push_notification?expand=1

I've just tested it and it works on mine, but as is always with push notifications you need to jump through a couple of certificate hoops.

I'm assuming you have an apple developer account, since that's the only way to support push notifications. Anyway, to make this work, you should:

  1. Do all the push related certificate setup from developer.apple.com (like generating certificates and profile)
  2. Download this push notification branch
  3. Go to Capabilities tab in XCode and enable Push Notifications => This will create an entitlements file automatically
  4. Go to General tab in XCode and DISABLE "automatically manage signing" and manually set that to the production app profile you created from developer.apple.com. (I think this is a one of those bugs on Apple's end and have seen many other projects suffer from the same problem. If anyone knows how to get around this please share.)

Note that these are not Jasonette related steps but something anyone who wants to implement push notification on iOS needs to go through.


Once you're all set up, this is how the new API should work:

  1. $notification.register action => Registers this device to Apple for push notification
  2. Then Apple server returns a device token => this triggers $notification.registered event with $jason.device_token set as the newly assigned device token
  3. You can use this value to do whatever you want. In the following example we send it to our server. It will probably store the device_token under current user's database entry
  4. Also, when the user receives a push notification while the app is in the foreground, it triggers $notification.remote event. In this case the entire push payload will be stored inside $jason. you can utilize this value to run any other action. In this case we call a $util.banner.
     {
         "$jason": {
            "head": {
                "title": "PUSH TEST",
                "actions": {
                    "$load": {
                        "type": "$notification.register"
                    },
                    "$notification.registered": {
                        "type": "$network.request",
                        "options": {
                            "url": "https://myserver.com/register_device.json",
                            "data": {
                                "device_token": "{{$jason.device_token}}"
                            }
                        }
                    },
                    "$notification.remote": {
                        "type": "$util.banner",
                        "options": {
                            "title": "Message",
                            "description": "{{JSON.stringify($jason)}}"
                        }
                    }
                }
            }
         }
     }

One more thing, I'm attaching the server-side code (it's an AWS lambda function but you can do whatever you want) I used to get it to work just in case. It's all hard-coded because it's just for validating the API:


// This app downloads the p8.key file from my S3 everytime, you can try it with your own S3. But anyway, downloading push certificate everytime is needless overhead so please don't do this in production.

var AWS = require('aws-sdk'); 
var s3 = new AWS.S3(); 
var fs = require('fs');
var apn = require('apn');
var options = {
  token: {
    key: "/tmp/key.p8",
    keyId: "[[REDACTED]]",
    teamId: "[[REDACTED]]"
  },
  production: true
};

var run = function(payload, ctx){
  var apnProvider = new apn.Provider(options);
  var note = new apn.Notification();
  note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
  note.sound = "pop.wav";
  note.badge = 3;
  note.alert = "\uD83D\uDCE7 \u2709 You have a new message";
  note.payload = {'messageFrom': 'Ethan Gliechtenstein'};
  note.topic = "[[YOUR APP BUNDLE ID]]";

  // Normally this device token should be dynamically looked up but I hard coded it by finding out what the device token is. I used some JSON markup like this to email myself the device token:
  // "actions": {"$notification.registered": {"type": "$href", "options": {"url": "mailto:ethan.gliechtenstein@gmail.com?subject={{$jason.device_token}}", "view": "app"}}}

  apnProvider.send(note, "[[HARDCODED DEVICE TOKEN]]").then( (result) => {
      // see documentation for an explanation of result
      console.log(JSON.stringify(result));
      ctx.succeed({ "response": "OK" })
  });
};

exports.handle = function(e, ctx) {
  // Need to download cert from S3 first
  var file = require('fs').createWriteStream('/tmp/key.p8');
  var key_dest = s3.getObject({Bucket: "jasonette", Key: "cert/key.p8"}).createReadStream();
  key_dest.on("data", function(data){
    file.write(data);
  });
  key_dest.on("end", function(data){
    file.end();
    run(e, ctx);
  });
}


If Anybody actually tries this out, please share the results. I would appreciate it. Then I should be able to merge this to master much faster.

Won't close this until I merge this with master.

Also, please feel free to point out if I'm missing anything from the diff

@seletz

This comment has been minimized.

Contributor

seletz commented Nov 7, 2016

Oh WOW.

@elfeffe

This comment has been minimized.

elfeffe commented Nov 7, 2016

I will try it
Thank you very much for this and for the whole project, this is a fantastic project.

@seletz

This comment has been minimized.

Contributor

seletz commented Nov 7, 2016

Ok, so I allocated some time to get this working with Azure Notification Hub -- just because we happen to have some Azure stuff running internally. I probably need to hack the code a bit for that to work, will do on a separate branch.

@seletz

This comment has been minimized.

Contributor

seletz commented Nov 16, 2016

Just wanted to mention that this code works beautifully. I think this issue could be closed now.

@gliechtenstein

This comment has been minimized.

Contributor

gliechtenstein commented Nov 17, 2016

I also confirm that this works fine, but there's one thing that's been bothering me, which is why I didn't merge this to master yet: #53

I hope some has a good solution for this!

@tomershvueli

This comment has been minimized.

tomershvueli commented Apr 2, 2017

I was able to use this branch and push notifications worked well! I did have to enable the 'capability' manually. Also, I am using Firebase for pushes, so I had to adjust the JasonAppDelegate to register with the proper endpoints.

@mrshawnj

This comment has been minimized.

mrshawnj commented Jul 2, 2018

this version seems to be missing file:// .... how do we reference local files from settings.plist?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment