Giter Club home page Giter Club logo

grocer's Introduction

Grocer

Gem Version Code Climate Build Status Dependency Status

grocer interfaces with the Apple Push Notification Service to send push notifications to iOS devices.

There are other gems out there to do this, but grocer plans to be the cleanest, most extensible, and friendliest.

Important Note

iOS 9.0 (and subsequent versions) introduced a subtle change to the way push tokens are provided.

If you delete and then re-install an application the push token is invalidated and a new push token is generated. This is important because the Feedback service does not deliver the list of invalidated tokens quickly enough to prevent you from using the now invalidated token.

There is currently a bug in grocer (#14) that will cause the APNS socket connect to hang up and fail to send subsequent notifications when one of these invalid tokens is used.

This bug combined with the change to push tokens in iOS results in varied reliability of push notification delivery. This may or may not affect you, but if you are seeing a large amount of undelivered notifications - specifically when sending multiple messages in quick succession - it is likely that you are coming up against this.

We are looking for help moving over to Apple's HTTP/2 notification API which should address this situation. The current maintainer doesn't have time to do this work, but please leave us a note if you would like to drive the effort.

Supported Rubies

  • Ruby/MRI 2.x
  • JRuby (latest)

Probably-supported Rubies

  • Rubinius 2 & 3

In truth, it's become hard to keep TravisCI building Rubinius versions, so we're officially removing it from our build pipeline. Things probably work, but if things break, feel free to keep both pieces.

Installation

Add this line to your application's Gemfile:

gem 'grocer'

If you are using JRuby, you will also need to add this to enable full OpenSSL support:

gem 'jruby-openssl'

Usage

Connecting

# `certificate` is the only required option; the rest will default to the values
# shown here.
#
# Information on obtaining a `.pem` file for use with `certificate` is shown
# later.
pusher = Grocer.pusher(
  certificate: "/path/to/cert.pem",      # required
  passphrase:  "",                       # optional
  gateway:     "gateway.push.apple.com", # optional; See note below.
  port:        2195,                     # optional
  retries:     3                         # optional
)

Notes

  • certificate: If you don't have the certificate stored in a file, you can pass any object that responds to read. Example: certificate: StringIO.new(pem_string)
  • gateway: Defaults to different values depending on the RAILS_ENV or RACK_ENV environment variables. If set to production, defaults to gateway.push.apple.com, if set to test, defaults to localhost (see Acceptance Testing later), otherwise defaults to gateway.sandbox.push.apple.com.
  • retries: The number of times grocer will retry writing to or reading from the Apple Push Notification Service before raising any errors to client code.

Sending Notifications

# `device_token` and either `alert` or `badge` are required.
#
# Information on obtaining `device_token` is shown later.
notification = Grocer::Notification.new(
  device_token:      "fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2",
  alert:             "Hello from Grocer!",
  badge:             42,
  category:          "a category",         # optional; used for custom notification actions
  sound:             "siren.aiff",         # optional
  expiry:            Time.now + 60*60,     # optional; 0 is default, meaning the message is not stored
  identifier:        1234,                 # optional; must be an integer
  content_available: true,                 # optional; any truthy value will set 'content-available' to 1
  mutable_content:   true                  # optional; any truthy value will set 'mutable-content' to 1
)

pusher.push(notification) # return value is the number of bytes sent successfully

It is desirable to reuse the same connection to send multiple notifications, as is recommended by Apple.

pusher = Grocer.pusher(connection_options)
notifications.each do |notification|
  pusher.push(notification)
end

Custom Payloads

The Apple documentation says "Providers can specify custom payload values outside the Apple-reserved aps namespace." To specify a custom payload, set Grocer::Notification#custom.

notification = Grocer::Notification.new(
  device_token: "...",
  alert:        "Hello from Grocer",
  custom: {
    "acme2": ["bang", "whiz"]
  }
)

# Generates a JSON payload like:
# {"aps": {"alert": "Hello from Grocer"}, "acme2": ["bang", "whiz"]}

Passbook Notifications

A Grocer::PassbookNotification is a specialized kind of notification which does not require any payload. That is, you need not (and Apple explicitly says not to) send any payload for a Passbook notification. If you do, it will be ignored.

notification = Grocer::PassbookNotification.new(device_token: "...")
# Generates a JSON payload like:
# {"aps": {}}

Newsstand Notifications

Grocer also supports the special Newsstand 'content-available' notification. Grocer::NewsstandNotification can be used for this. Like Grocer::PassbookNotification, it is a specialized kind of notification which does not require any payload. Likewise, anything you add to it will be ignored.

notification = Grocer::NewsstandNotification.new(device_token: "...")
# Generates a JSON payload like:
# {"aps": {"content-available":1}}

Safari Notifications

Grocer can be used for Safari Push Notifications introduced in Mavericks.

notification = Grocer::SafariNotification.new(
  device_token: '...',        # required
  title: 'Hello from Grocer', # required
  body: 'Hi',                 # required
  action: 'Read',             # optional; the label of the action button
  url_args: ['arg1']          # required (array); values that are paired with the placeholders inside the urlFormatString.
)

Generates a JSON payload like:

{
  "aps": {
    "alert": {
      "title": "Hello from Grocer",
      "body": "Hi",
      "action": "Read"
    },
    "url-args": [ "arg1" ]
  }
}

Feedback

# `certificate` is the only required option; the rest will default to the values
# shown here.
feedback = Grocer.feedback(
  certificate: "/path/to/cert.pem",       # required
  passphrase:  "",                        # optional
  gateway:     "feedback.push.apple.com", # optional; See note below.
  port:        2196,                      # optional
  retries:     3                          # optional
)

feedback.each do |attempt|
  puts "Device #{attempt.device_token} failed at #{attempt.timestamp}"
end

Notes

  • gateway: Defaults to feedback.push.apple.com only when running in a production environment, as determined by either the RAILS_ENV or RACK_ENV environment variables. In all other cases, it defaults to the sandbox gateway, feedback.sandbox.push.apple.com.
  • retries: The number of times grocer will retry writing to or reading from the Apple Push Notification Service before raising any errors to client code.

Acceptance Testing

Grocer ships with framework to setup a real looking APNS server. It listens on a real SSL-capable socket bound to localhost. See the Connecting Notes above for details.

You can setup an APNS client to talk to it, then inspect the notifications the server received.

The server simply exposes a blocking queue where notifications are placed when they are received. It is your responsibility to timeout if a message is not received in a reasonable amount of time.

For example, in RSpec:

require 'timeout'

describe "apple push notifications" do
  before do
    @server = Grocer.server(port: 2195)
    @server.accept # starts listening in background
  end

  after do
    @server.close
  end

  specify "As a user, I receive notifications on my phone when awesome things happen" do
    # ... exercise code that would send APNS notifications ...

    Timeout.timeout(3) {
      notification = @server.notifications.pop # blocking
      expect(notification.alert).to eq("An awesome thing happened")
    }
  end
end

Device Token

A device token is obtained from within the iOS app. More details are in Apple's Registering for Remote Notifications documentation.

The key code for this purpose is:

- (void)applicationDidFinishLaunching:(UIApplication *)app {
   // other setup tasks here....
   [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
    NSLog(@"Got device token: %@", [devToken description]);

    [self sendProviderDeviceToken:[devToken bytes]]; // custom method; e.g., send to a web service and store
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
    NSLog(@"Error in registration. Error: %@", err);
}

Certificate File

Login to the iOS Provisioning Portal (App IDs).

Select your app bundle, scroll down and click "Edit." This will bring you to a page that will allow you to setup and configure services for your app. Scroll down until you see the service labeled "Push Notifications." Depending on whether you’re creating a Development or Production certificate, click the appropriate "Create Certificate" button.

Creating the Push Notification Certificate

Follow the instructions to create a Certificate Signing Request, click "Continue," upload the CSR, and download the resulting .cer file.

Open the .cer file in Keychain Access, then expand the certificate to show both the certificate and the private key. Command-select so both are highlighted:

Selecting both the certificate and private key

Control click and select to export the 2 items:

Exporting the certificate and private key

Save the items as a .p12 file. Open a terminal window and run the following command:

openssl pkcs12 -in exported_certificate.p12 -out certificate.pem -nodes -clcerts -des3

You will be prompted for two password. The first one is the password that you used when you exported the private key and certificate from Keychain Access. The second password will be used to encrypt and lock the private key. This will be the passphrase used when configuring grocer to connect to APNs.

The certificate.pem file that is generated can be used with grocer.

Alternative way to generate certificate file (no Mac OS X required)

Generate private key and unsigned certificate:

 openssl req -nodes -newkey rsa:2048 -keyout push_private_key.pem -out push.csr
  • Go to Apple Developer site and select Add iOS Certificate.
  • Choose Apple Push Notification service SSL (Sandbox & Production).
  • Upload the push.csr file during the Generate your certificate step.
  • Download aps.cer on the next step.

Create push.pem file from aps.cer with following command:

openssl x509 -in aps.cer -inform der -out push.pem

Merge push.pem file and your private key into certificate.pem:

cat push.pem push_private_key.pem > certificate.pem

Support Channels

GitHub Issues and Pull Requests are the primary venues for communicating issues and discussing possible features. Several of us also regularly hang out in the #grocer channel on Freenode; feel free to pop in and ask questions there as well. Thanks! ❤️

grocer's People

Contributors

adamvduke avatar alindeman avatar benrugg avatar benubois avatar betamatt avatar cmason avatar davidmccoy avatar eychu avatar fredjean avatar johnnaegle avatar kbrock avatar khelll avatar kyledrake avatar lgleasain avatar mat avatar mbillard avatar oriolgual avatar overlycommonname avatar patr1ck avatar petergoldstein avatar snatchev avatar somezack avatar stevenharman avatar useruby avatar vanstee avatar wschenk avatar wxianfeng avatar yjukaku 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grocer's Issues

Creating a Notification with RSpec

Hello,

I'm having trouble getting a very simple spec working with constructing a notification. The ... exercise code that would send APNS notifications ... of the readme. Creating a pusher works fine, as far as I can tell, but creating a notification seems to be missing some required file or include.

What I'm getting now:

2) ApnsRegistration notification creation tries to create a notification
     Failure/Error: n = Grocer::Notification.new(device_token: "testing", alert: "Yo dawg")
     ArgumentError:
       wrong number of arguments (1 for 0)
     # ./spec/models/apns_registration_spec.rb:95:in `initialize'
     # ./spec/models/apns_registration_spec.rb:95:in `new'
     # ./spec/models/apns_registration_spec.rb:95:in `block (3 levels) in <top (required)>'

The spec that produced this:

require 'spec_helper'

describe ApnsRegistration do
  describe "notification creation" do
    it "tries to create a notification" do
      n = Grocer::Notification.new(device_token: "testing", alert: "Yo dawg")
    end
  end
end

Note that I've also thought of the length of the device_token at this point, and making it the same length of a regular device token has no effect.

I have tried adding require 'grocer', and require 'grocer/notification' in my spec_helper, with no luck.

The acceptance testing block on the readme is helpful, but I fear it may be lacking something WRT creating Grocer objects.

Thanks!

Notifications not received with development app identifier

Hi there,

I configured my Rails app and iPhone to send and receive notifications. Unfortunately when I send a notification (taken from the readme) the push command returns 137. I can't find what this code means. Any ideas?

Update:
Changing the notification changes the returned value. So this seems to be the amount of bytes I guess?

The real issue is that notifications aren't coming through to my iPhone and I am not sure where to start debugging. Any hints?

Update:
Changed issue name from : Push returns status 137 to be more descriptive for future reference.

Documentation: How to use with Sidekiq and how to support multiple applications

I'm looking into various options for gems that support push notification.

I'd like to make sure whatever option I go with supports (or can easily be made to support) a persistent connection to Apple's server.

Also, in my case I need to support notifications to separate apps (iphone, and ipad - potentialy also free and paid) which unfortunately require separate certificates from Apple.

Can grocer support these features (without extensive modification)? If so, it would be great to see some documentiation, perhaps inclusion in the readme, on how to set them up. I'd imagine these are fairly common use cases.

Understanding feedback

I have got two questions concerting analysing feedback.

  1. How can I analyse the feedback I receive from apple.

<% @feedback.each do |attempt| %>
<%= "Device #{attempt.device_token} failed at #{attempt.timestamp}" %>

<% end %>

Is there a complete list of attributes for @feedback. What do you guys do with failed attempts?

  1. How do you handle the result if you send a notification?

result = pusher.push(notification)
result has always a number which I do not understand

101
102
104
106
126
145
161
167

Where can I find a translation for these codes...

Thx for supporting :).

Handling APN with delay

We have a worker that sends push notification for a given push ID.
The worker gets messages and sends PN according to the data.

The problem:
Sometimes the PN server responds with big delay. It got stuck in a middle of sending PN (maybe they limit number of APNS). It can take about an hour or more, and then, the PN is sent successfully and the queue started polling messages again..
But in this time, the worker can't push any message from the queue, so we have a big delay also for the other devices.

Is there a timeout option to Grocer.pusher ? (It's not appears in documentation)
Any other idea how to handle this?

Heroku server restart fixes problem with pushes not going through - troubleshooting help

After a while my Heroku App pushes stop actually going through to the respective devices. A Heroku Dyno 'restart' fixes this problem (essentially a server restart).

I have created a log table and can see that I have sent the pushes to Grocer with the correct device tokens.

I am using a class variable to keep the pusher, like so:

@@pusher

What is the easiest way for me to find the cause of this problem?

I would love to have some code that could 'catch' this failure rather than processing successfully (does Grocer already have this?).

Thanks !

Triggering APN on New Message

I'm curious, and I just may not be seeing it in the explanation, of where I would put the trigger to send the APN when a new Message is received. Just as a brief explanation, I want to send a push notification to a user when he receives a new message. Can anyone provide me with some assistance on how to make this work?

OpenSSL::PKey::RSAError: Neither PUB key nor PRIV key:: nested asn1 error

Hi,
I've direcly exported the push development certificate to pem but when I try to get the feedback I have an output like this

OpenSSL::PKey::RSAError: Neither PUB key nor PRIV key:: nested asn1 error
from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/grocer-0.4.1/lib/grocer/ssl_connection.rb:35:in initialize' from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/grocer-0.4.1/lib/grocer/ssl_connection.rb:35:innew'
from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/grocer-0.4.1/lib/grocer/ssl_connection.rb:35:in connect' from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/grocer-0.4.1/lib/grocer/connection.rb:29:inconnect'
from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/grocer-0.4.1/lib/grocer/connection.rb:55:in with_connection' from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/grocer-0.4.1/lib/grocer/connection.rb:17:inread'
from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/grocer-0.4.1/lib/grocer/feedback.rb:12:in each' from (irb):3 from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.14/lib/rails/commands/console.rb:47:instart'
from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.14/lib/rails/commands/console.rb:8:in start' from /home/oz/.rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.14/lib/rails/commands.rb:41:in<top (required)>'
from script/rails:6:in require' from script/rails:6:in

'

Notification validation is overzealous

I have occasion to send APNs with neither alerts nor badges (but with custom content). I understand that this is a somewhat strange use of APNs, but it is valid, and, after I monkey-patched Grocer to not try to validate presence of alerts or badges, Apple accepted and correctly delivered the APN.

I would be happy to submit a pull request, but since it's pure code removal, thought I'd talk it over first. The warning about APNs with neither badges nor alerts seems useful in most cases, and wasn't sure how people would feel about removing it.

Feeding certificate as giant string

Is this possible? I have the entire cert set as an environment variable and would like to jus feed grocer ENV['APPLE_PUSH_NOTIFICATION_CERT'].

Add Changelog please

Will be nice if the project is going to be here for the long haul. It really helps when updating/upgrading.

Allow options hash to use symbols or strings for keys

Often we'll have a mixed-bag of symbols and strings as hash keys, and clients currently need to do something like this to ensure Grocer always get the symbols it expects:

feedback = Grocer.feedback(SETTINGS['push_notifications'].symbolize_keys)

NOTE: the following will not work due to some internal merging of hashes that Grocer does:

feedback = Grocer.feedback(SETTINGS['push_notifications'].with_indifferent_access)

From an API perspective, perhaps we can make a small enhancement to Grocer to ensure strings are also handled as hash keys for the options to Grocer.feedback, Grocer.pusher, etc.?

Server Connections

I'm currently using APND. It works, but i'm not super pumped that I have to manage and keep an extra daemon process up. The reason claimed for doing that was so that it could prevent my app from connecting to Apple multiple times very quickly if the need on our side arises.

We do send notifications in bulk sometimes, but at most other times, pushing notifications is not that frequent (i.e. it's mostly one-offs). I know when i'm sending in bulk and when i'm not.

I've been on the look out for something can take away that extra moving part from my production landscape.

Is this gem it? Can it keep me safe from getting blocked at Apple while not having to maintain and keep up another daemon process? Reading the README, certainly makes me believe it is.

SSL Error, Certificate Unknown

When I send a message I get this error in the sidekiq console,

SSL_connect returned=1 errno=0 state=SSLv3 read finished A: sslv3 alert certificate unknown

I saw a similar error for people using apn_on_rails and they thought it had something to do with the certificate which someone redid and it worked. I tried doing the same but still get the same error. Any idea what it could be?

Support Simple and Enhanced Notification Formats

While looking into Issue #21 I realized there are 2 specific notification formats: Simple and Enhanced. It might be nice to also allow notifications that leave out the Identifier or Expiry in the payload (Simple format) since we only currently support the Enhanced version. I'd be ok with also carrying over the domain terms into new SimpleNotificiation and EnhancedNotification classes. Thoughts?

the validate_payload method on notification does not make sense for passbook push notifications.

I needed to monkey patch notification to get it to work correctly with passbook updates.

I can see two ways to solve this.

  1. Allow for an optional attribute to be passed to notification which controls how the payload validation happens (which will cause passbook notifications to not validate).
  2. Eliminate the send_notification method.

Let me know which you would prefer (if any) and I would be happy to submit a pull request.

Is Grocer thread safe?

Hi,

Sorry to use your issue tracker to ask a question. We use Sidekiq in our queues, which means we're running 25 concurrent threads. If we had a single PushConnection set up during initialization, would all the Sidekiq workers be able to use this, or do we need to write a connection pool class that wraps grocer?

Cheers,

Chris

Delayed job integration

Dears,

I'm trying grocer with delayed_job. I'm having a very weird case where I send notifications through a delayed job:

I setup the connection and then push, but nothing happens till I inspect the connection, something like:

Delayed::Worker.logger.info connection.inspect

Once, I do so it works. Without that, nothing happens at all.

If i try the same connection initialization from Ruby or from the same Rails project, everything works well and as expected. It's just when the whole thing is running inside a delayed job.

Any ideas?

Another question, I don't think it's optimal to create a connection for each job, I was thinking of creating a general constant form the rails initializer and use it in all jobs. Do you this is a valid idea?

How should I do error handling with grocer?

I found that if I write a push notification with an old push token ( not an invalid one, just an outdated one. ) to the connection, and the connection is useless. The following push notifications write to the connection are not pushed to the users.

Then I tried with Grocer::Connection, after I write the old push token, I can get \b\b\x00\x00\x00\x00 if I read from the stream. And then if I call conn.connect to connect again, the connection can be used for other push notifications, otherwise, whatever notification writes to the connection is not pushed to the user.

What should I do with this? Do I have to read from the stream and handle this or Grocer has something which already takes care of it for me?

Thanks a lot.

feedback.each does not iterate after feedback.count

If you call the .count method on the Grocer.feedback object, it looks like it then prevents the .each method from running.

For example:

feedback = Grocer.feedback(opts)
puts "Apple's feedback service says that #{feedback.count} devices have failed push notifications"
feedback.each do |attempt|
  puts "Device #{attempt.device_token} failed at #{attempt.timestamp}"
end

This code will only output the first line and not run anything inside the .each loop.

push fails with errorcode 103

Hi there, whenever I try to push a notification, it fails silently, with return code 103. My understanding is that this errorcode is actually returned by the OpenSSL::SSL::SSLSocket instance. Any idea what might be the issue here?

FYI

Hey all-

Grocer is great. Thanks for all the hard work. I put a light layer over it to make it look like actionmailer in rails projects, may add in a generator as well

https://github.com/thejchap/push_notifier

Just wanted to send your way, let me know if you want more credit in the readme or anything like that, or if you think it would be more appropriate as a fork of Grocer than a separate gem. Obviously not much heavy lifting on my end... 👍

Rock on

Review and refactor the internals of Grocer::Server, Grocer::SSLServer

We've run into weird issues, especially on different platforms (e.g., Linux vs. OS X) dealing with setting up and tearing down a Grocer.server.

This issue is to audit the code that's currently written, looking for ways to improve the ways the server socket is setup and used. We should consider stuff like whether using non-blocking/async calls is more appropriate, and how to make sure that all platforms react well to the changes.

Support Ruby 2.0

Right now I'm seeing these failures on Ruby 2.0.0-p0:

Failures:

  1) Grocer::Connection raises CertificateExpiredError for OpenSSL::SSL::SSLError with /certificate expired/i message
     Failure/Error: -> {subject.write('abc123')}.should raise_error(Grocer::CertificateExpiredError)
       expected Grocer::CertificateExpiredError, got #<OpenSSL::SSL::SSLError: certificate expired> with backtrace:
         # ./lib/grocer/connection.rb:24:in `block in write'
         # ./lib/grocer/connection.rb:58:in `with_connection'
         # ./lib/grocer/connection.rb:23:in `write'
         # ./spec/grocer/connection_spec.rb:61:in `block (3 levels) in <top (required)>'
         # ./spec/grocer/connection_spec.rb:61:in `block (2 levels) in <top (required)>'
     # ./spec/grocer/connection_spec.rb:61:in `block (2 levels) in <top (required)>'

  2) Grocer::Connection retries retries #read in the case of an SocketError
     Failure/Error: subject.read
     SocketError:
       SocketError
     # ./lib/grocer/connection.rb:18:in `block in read'
     # ./lib/grocer/connection.rb:58:in `with_connection'
     # ./lib/grocer/connection.rb:17:in `read'
     # ./spec/grocer/connection_spec.rb:102:in `block (4 levels) in <top (required)>'

  3) Grocer::Connection retries retries #write in the case of an SocketError
     Failure/Error: subject.write('abc123')
     SocketError:
       SocketError
     # ./lib/grocer/connection.rb:24:in `block in write'
     # ./lib/grocer/connection.rb:58:in `with_connection'
     # ./lib/grocer/connection.rb:23:in `write'
     # ./spec/grocer/connection_spec.rb:107:in `block (4 levels) in <top (required)>'

  4) Grocer::Connection retries retries #read in the case of an OpenSSL::SSL::SSLError
     Failure/Error: subject.read
     OpenSSL::SSL::SSLError:
       OpenSSL::SSL::SSLError
     # ./lib/grocer/connection.rb:18:in `block in read'
     # ./lib/grocer/connection.rb:58:in `with_connection'
     # ./lib/grocer/connection.rb:17:in `read'
     # ./spec/grocer/connection_spec.rb:102:in `block (4 levels) in <top (required)>'

  5) Grocer::Connection retries retries #write in the case of an OpenSSL::SSL::SSLError
     Failure/Error: subject.write('abc123')
     OpenSSL::SSL::SSLError:
       OpenSSL::SSL::SSLError
     # ./lib/grocer/connection.rb:24:in `block in write'
     # ./lib/grocer/connection.rb:58:in `with_connection'
     # ./lib/grocer/connection.rb:23:in `write'
     # ./spec/grocer/connection_spec.rb:107:in `block (4 levels) in <top (required)>'

  6) Grocer::Connection retries retries #read in the case of an Errno::EPIPE
     Failure/Error: subject.read
     Errno::EPIPE:
       Broken pipe
     # ./lib/grocer/connection.rb:18:in `block in read'
     # ./lib/grocer/connection.rb:58:in `with_connection'
     # ./lib/grocer/connection.rb:17:in `read'
     # ./spec/grocer/connection_spec.rb:102:in `block (4 levels) in <top (required)>'

  7) Grocer::Connection retries retries #write in the case of an Errno::EPIPE
     Failure/Error: subject.write('abc123')
     Errno::EPIPE:
       Broken pipe
     # ./lib/grocer/connection.rb:24:in `block in write'
     # ./lib/grocer/connection.rb:58:in `with_connection'
     # ./lib/grocer/connection.rb:23:in `write'
     # ./spec/grocer/connection_spec.rb:107:in `block (4 levels) in <top (required)>'

  8) Grocer::ErrorResponseChecker.check_for_responses reads from the connection
     Failure/Error: expect(connection).to have_received(:read).with(Grocer::ErrorResponse::LENGTH)
       unstubbed, expected exactly once, not yet invoked: #<Mock:Connection>.read(6)
     # ./spec/grocer/error_response_checker_spec.rb:30:in `block (3 levels) in <top (required)>'

Segmentation fault in test server

I've started seeing segmentation faults during test runs of the Grocer server on our CI platform. These do not appear locally, and so far not on production either. The CI and production platform are Ubuntu 12.04 and the Ruby version is identical for all, 2.1.1.

Is this a problem in Grocer or do I have to look elsewhere?

/home/rof/.rvm/rubies/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:61: [BUG] Segmentation fault at 0x007f26d801dcb0
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0007 p:---- s:0029 e:000028 CFUNC  :sysread
c:0006 p:0019 s:0025 e:000023 METHOD /home/rof/.rvm/rubies/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:61
c:0005 p:0085 s:0021 e:000020 METHOD /home/rof/.rvm/rubies/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:102
c:0004 p:0010 s:0015 e:000014 METHOD /home/rof/cache/bundler/ruby/2.1.0/gems/grocer-0.6.0/lib/grocer/notification_reader.rb:21
c:0003 p:0020 s:0008 e:000007 METHOD /home/rof/cache/bundler/ruby/2.1.0/gems/grocer-0.6.0/lib/grocer/notification_reader.rb:13
c:0002 p:0026 s:0004 e:000003 BLOCK  /home/rof/cache/bundler/ruby/2.1.0/gems/grocer-0.6.0/lib/grocer/server.rb:25 [FINISH]
c:0001 p:---- s:0002 e:000001 TOP    [FINISH]

-- Ruby level backtrace information ----------------------------------------
/home/rof/cache/bundler/ruby/2.1.0/gems/grocer-0.6.0/lib/grocer/server.rb:25:in `block (3 levels) in accept'
/home/rof/cache/bundler/ruby/2.1.0/gems/grocer-0.6.0/lib/grocer/notification_reader.rb:13:in `each'
/home/rof/cache/bundler/ruby/2.1.0/gems/grocer-0.6.0/lib/grocer/notification_reader.rb:21:in `read_notification'
/home/rof/.rvm/rubies/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:102:in `read'
/home/rof/.rvm/rubies/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:61:in `fill_rbuff'
/home/rof/.rvm/rubies/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:61:in `sysread'

-- C level backtrace information -------------------------------------------
/home/rof/.rvm/rubies/ruby-2.1.1/lib/libruby.so.2.1(+0x1c73dc) [0x7f26ec4a23dc] vm_dump.c:685
/home/rof/.rvm/rubies/ruby-2.1.1/lib/libruby.so.2.1(+0x74410) [0x7f26ec34f410] error.c:307
/home/rof/.rvm/rubies/ruby-2.1.1/lib/libruby.so.2.1(rb_bug+0xb3) [0x7f26ec3500c3] error.c:334
/home/rof/.rvm/rubies/ruby-2.1.1/lib/libruby.so.2.1(+0x148dde) [0x7f26ec423dde] signal.c:704
/lib/x86_64-linux-gnu/libc.so.6(+0x36c30) [0x7f26ebf4bc30] array.c:759
[0x7f26d801dcb0]

Feedback gives missing output / wrong count ( half of the devices)

Hello, i m currently testing with both of my iphone (iphone 6 - os 8.1.1, and iphone 5 - os 7.1.1 ) and even thou i deleted my application from both of them, when i tried to send notification on those devices it only retunrs iPhone 6's device token.

What should i investigate? Is it a problem from phone model? or maybe ios Version?

Socket connection drops next notification if invalid notification is sent

So, this is the problem I ran into on Lead Zeppelin (https://github.com/geoloqi/lead_zeppelin). It's kindof a big problem right now, and I haven't figured out a graceful way to fix it yet.

If you send an illegit message (such as a message with a bad device token), the connection will drop without throwing an exception for the next message, and that next message will effectively be dropped.

Example:

pusher = Grocer.pusher(certificate: "/path/to/validcert.pem")
notification = Grocer::Notification.new(device_token: "valid device token", alert: 'this will work')
pusher.push(notification)
notification = Grocer::Notification.new(device_token: "bad device token", alert: 'this will fail, as expected')
pusher.push(notification)
notification = Grocer::Notification.new(device_token: "valid device token", alert: 'THIS WILL UNEXPECTEDLY FAIL WITH NO EXCEPTIONS OR WARNINGS')
pusher.push(notification)
notification = Grocer::Notification.new(device_token: "valid device token", alert: 'this will work because an exception will be thrown, reconnecting the socket')
pusher.push(notification)

I don't know what the source of this problem is.. whether it's something in Ruby, something on Apple's APNS servers, or some sort of buffering issue, or what.

I worked around this problem by putting in an IO.select to wait for a specific period of time, to see if the socket becomes available for reading. With the enhanced protocol, being readable indicates that there was an error. But the only way to do this is to wait for a response before using it again, due to the silent failure. But this solution sucks, because it slows everything down.

You can see the implementation of that IO.select here: https://github.com/geoloqi/lead_zeppelin/blob/master/lib/lead_zeppelin/apns/gateway.rb#L96

The solution I was pondering to deal with this problem was to switch back to the version 0 protocol, but then of course you don't get the enhanced error response, which also kindof sucks.

The real solution would be for APNS to send a confirmation bit like they should, or drop the connection correctly... but that's not going to happen anytime soon. Really at a loss for how to solve this.

Grocer should reconnect after an Errno::EPIPE

After a while, the connection can be closed with an EPIPE:

1.9.3p125 :029 > pusher.push(Grocer::Notification.new(device_token: "abc123", alert: "Hi!"))
Errno::EPIPE: Broken pipe
        from /Users/alindeman/.rvm/rubies/ruby-1.9.3-p125/lib/ruby/1.9.1/openssl/buffering.rb:318:in `syswrite'
        from /Users/alindeman/.rvm/rubies/ruby-1.9.3-p125/lib/ruby/1.9.1/openssl/buffering.rb:318:in `do_write'
        from /Users/alindeman/.rvm/rubies/ruby-1.9.3-p125/lib/ruby/1.9.1/openssl/buffering.rb:336:in `write'

Why is payload_too_large? and the actual size of the payload not public?

We currently try to truncate the alert when exceeding the payload dimension, but without actual numbers it's kind of difficult.

We're left with 2 options:

  • fork and expose payload_too_large? and a new payload_size (which is encoded_payload.bytesize)
  • use send to access private methods

I wanted to know if there was any reason for making these private. If not, then you can expect a PR promptly.

Thanks

Ad Hoc builds should use production gateway

Our app has a staging environment / staging build, distributed via Ad Hoc. According to StackOverflow[1] / Apple's docs[2], ad hoc-distributed builds use the production push gateway, not the dev push gateway.

What do you recommend re: Grocer.env and the behavior of find_default_gateway in push_connection?

1: http://stackoverflow.com/questions/1698951/iphone-push-notification-problem-with-ad-hoc-provision
2: https://developer.apple.com/library/ios/#technotes/tn2265/_index.html, search for "ad hoc"

push feedback

if I send the push , How to verify that it was sent successfully?

How the server know whether the push notification received by iPhone(and also the action that user performed (tapping "Cancel" or "View")). Do i need to send separate message from client to server that "Notification received" or Is There any mechanism to get status from APNS by server so that I can avoid sending a message from client side.
I have checked enter and Push notification guide, but I didn't find any information.

Support Newsstand Notifications

Similar to #27, Newsstand's content-available notifications require only a special "content-available" key with a value of 1, like so:

notification = Grocer::Notification.new(
        device_token: device.token,
        custom: {
          "content-available" => 1
        }
      )

Trying to create this causes a validation error because of the lack of alert or badge. It'd be cool to have a special notification type for Newsstand as well.

TLS only starting 2014-10-29

In response to Poodle, Apple only allows TLS connections starting on 2014-10-29. So, no more SSLv3. I couldn't really find something related in the source code, but are we already safe from that?

nested asn1 error

I'm having a little problem with the grocer gem.
I followed the documentation's instructions, with my cert.pem file, but got:
nested asn1 error

my code:

pusher = Grocer.pusher(certificate: "#{Rails.root}/lib/dev_cert.pem", 
               passphrase:  "XXX",                      
               gateway:     "gateway.sandbox.push.apple.com", 
               retries:     3)

feedback = Grocer.feedback(FEEDBACK_OPTIONS)
notification = Grocer::Notification.new(
                     device_token:    push_id,
                     alert:               message,
                     badge:             3,
                     sound:             "siren.aiff", 
                     expiry:            Time.now + 60*60, 
                     content_available: true        
                     )

feedback.each do |attempt|
puts "Device #{attempt.device_token} failed at #{attempt.timestamp}"
end         
pusher.push(notification)

ruby version - 1.9.3
grocer version - 0.5.0

More details here: http://stackoverflow.com/questions/23760894/ror-nested-asn1-error-while-sending-push-notification-via-grocer-gem

Can grocer support multiple applications?

This is a question, sorry to be using your issue tracker for it.

I am using Grocer in combination with sidekiq and my application currently needs to communicate to two iOS applications.

Would creating a grocer instance on multiple threads cause an issue with the provisioning (multiple certificates) and is there a recommended way to support push notifications for more than one application using Grocer?

Grocer should reconnect after an SSLError

Sometimes the connection can be reset by an SSLError. Grocer should reconnect:

1.9.3p125 :032 > pusher.push(Grocer::Notification.new(device_token: "abc123", alert: "Hi!"))
OpenSSL::SSL::SSLError: SSL_write:: bad write retry
        from /Users/alindeman/.rvm/rubies/ruby-1.9.3-p125/lib/ruby/1.9.1/openssl/buffering.rb:318:in `syswrite'
        from /Users/alindeman/.rvm/rubies/ruby-1.9.3-p125/lib/ruby/1.9.1/openssl/buffering.rb:318:in `do_write'
        from /Users/alindeman/.rvm/rubies/ruby-1.9.3-p125/lib/ruby/1.9.1/openssl/buffering.rb:336:in `write'

Unsure of correct usage of feedback connection

I wrote a small script to test the handling of errors using grocer’s feedback mechanism. If I use a production token, it is correctly sent and received.

To test for feedback, I send a notification to a development token using the production gateway, and ask the production feedback service for errors. Sadly, I don’t get any feedback from @feedback.each do |attempt|:

require 'grocer'

class Pusher
  def connect
    @pusher = Grocer.pusher(
      certificate: "apple_push_notification_prod.pem",
      gateway:     "gateway.push.apple.com",
      port:        2195,
      retries:     3
    )
  end

  def send(token)
    notification = Grocer::Notification.new(
      device_token:      token,
      alert:             "We should get feedback",
      badge:             42
    )
    @pusher.push(notification)

    @feedback = Grocer.feedback(
      certificate: "apple_push_notification_prod.pem",
      gateway:     "feedback.push.apple.com",
      port:        2196,
      retries:     3
    )
    @feedback.each do |attempt|
      puts "Device #{attempt.device_token} failed at #{attempt.timestamp}"
    end
  end
end

push_client = Pusher.new
push_client.connect
# Send to a development push notification token (token substituted for this snippet)
push_client.send("517ff994ea1a432a3fb8959017fd31bd052ba2bed3ff7e86dd4f44100c3163ca")

Am I using the feedback mechanism incorrectly (grocer 0.6.0)?

Sending notifications in batch

Apple guidelines say "For optimum performance, batch multiple notifications in a single transmission over the interface, either explicitly or using a TCP/IP Nagle algorithm."
Does your gem perform it? What would happen with my application if I am not sending messages in a batch? Thanks.

Using ErrorResponse class for handling specific errors

I found this class but can't find it using somewhere in the code. The reason I'm asking is Apple recommendation:

Note: The Apple Push Notification service (APNs) provides feedback to your server which may require specific actions. For example, if APNs tells you that a push token is invalid, remove that device and its registrations from your server

This is what that class is responsible for - tell me exactly what error was happened, so I can decide should I destroy registration of device or not.

Please, make this moment clear for me. Am I missed something or there is a way to use this class somehow?

Feedback not working?

I sent a notification with a bad device token. Shouldn't apple respond in the feedback method?

irb(main):002:0> device_token = "badtoken"
=> "badtoken"
irb(main):003:0>     notification = Grocer::Notification.new(
irb(main):004:1*        device_token: device_token,
irb(main):005:1*        alert:        "hillo"
irb(main):006:1> )
=> #<Grocer::Notification:0x007fa181ff1368 @identifier=0, @device_token="badtoken", @alert="hillo">
irb(main):007:0> IOS_PUSHER.push(notification)
=> 70
irb(main):008:0>     feedback = Grocer.feedback(IOS_PUSH_FEEBACK_OPTS)
=> #<Grocer::Feedback:0x007fa184282378 @connection=#<Grocer::Connection:0x007fa1842823a0 @certificate="/Users/t/src/web-mobile/config/mobile_certs/ios_development.pem", @passphrase="", @gateway="feedback.sandbox.push.apple.com", @port=2196, @retries=3>>
irb(main):009:0>     feedback.map do |attempt|
irb(main):010:1*       {ios_device_token: attempt.device_token, failed_at: attempt.timestamp}
irb(main):011:1>     end
=> []

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.