Technology moves fast! ⚡ Don't get left behind.🚶 Subscribe to our mailing list to keep up with latest and greatest in open source projects! 🏆


Subscribe to our mailing list

lowdown

A Ruby client for the HTTP/2 version of the Apple Push Notification Service.

Subscribe to updates I use lowdown


Statistics on lowdown

Number of watchers on Github 119
Number of open issues 5
Average time to close an issue 27 days
Main language Ruby
Average time to merge a PR about 23 hours
Open pull requests 3+
Closed pull requests 0+
Last commit over 2 years ago
Repo Created over 3 years ago
Repo Last Updated over 1 year ago
Size 166 KB
Organization / Authoralloy
Contributors2
Page Updated
Do you use lowdown? Leave a review!
View open issues (5)
View lowdown activity
View on github
Fresh, new opensource launches 🚀🚀🚀
Trendy new open source projects in your inbox! View examples

Subscribe to our mailing list

Evaluating lowdown for your project? Score Explanation
Commits Score (?)
Issues & PR Score (?)

Lowdown

Build Status

Lowdown is a Ruby client for the HTTP/2 version of the Apple Push Notification Service.

For efficiency, multiple notification requests are multiplexed and a single client can manage a pool of connections.

$ bundle exec ruby examples/simple.rb path/to/certificate.pem development <device-token>
Sent notification with ID: 13
Sent notification with ID: 1
Sent notification with ID: 10
Sent notification with ID: 7
Sent notification with ID: 25
...
Sent notification with ID: 10000
Sent notification with ID: 9984
Sent notification with ID: 9979
Sent notification with ID: 9992
Sent notification with ID: 9999
Finished in 14.98157 seconds

This example was run with a pool of 10 connections.

Installation

Add this line to your application's Gemfile:

gem 'lowdown'

Or install it yourself, for instance for the command-line usage, as:

$ gem install lowdown

Usage

You can use the lowdown bin that comes with this gem or for code usage see the documentation.

There are mainly two different modes in which youll typically use this client. Either you deliver a batch of notifications every now and then, in which case you only want to open a connection to the remote service when needed, or you need to be able to continuously deliver transactional notifications, in which case youll want to maintain persistent connections. You can find examples of both these modes in the examples directory.

But first things first, this is how you create a notification object:

notification = Lowdown::Notification.new(:token => "device-token", :payload => { :alert => "Hello World!" })

Theres plenty more options for a notification, please refer to the Notification documentation.

Short-lived connection

After obtaining a client, the simplest way to open a connection for a short period is by passing a block to connect. This will open the connection, yield the block, and close the connection by the end of the block:

client = Lowdown::Client.production(true, certificate: File.read("path/to/certificate.pem")
client.connect do |group|
  # ...
end

Persistent connection

NOTE: See the Gotchas section, specifically about process forking.

The trick to creating a persistent connection is to specify the keep_alive: true option when creating the client:

client = Lowdown::Client.production(true, certificate: File.read("path/to/certificate.pem"), keep_alive: true)

# Send a batch of notifications
client.group do |group|
  # ...
end

# Send another batch of notifications
client.group do |group|
  # ...
end

One big difference youll notice with the short-lived connection example, is that you no longer use the Client#connect method, nor do you close the connection (at least not until your process ends). Instead you use the group method to group a set of deliveries.

Grouping requests

Because Lowdown uses background threads to deliver notifications, the thread youre delivering them from would normally chug along, which is often not what youd want. To solve this, the group method provides you with a group object which allows you to handle responses for the requests made in that group and halts the caller thread until all responses have been handled.

All responses in a group will be handled in a single background thread, without halting the connection threads.

In typical Ruby fashion, a group provides a way to specify callbacks as blocks:

group.send_notification(notification) do |response|
  # ...
end

But theres another possiblity, which is to provide a delegate object which gets a message sent for each response:

class Delegate
  def handle_apns_response(response, context:)
    # ...
  end
end

delegate = Delegate.new

client.group do |group|
  group.send_notification(notification, delegate: delegate)
end

Keep in mind that, like with the block version, this message is sent on the groups background thread.

Threading

While were on the topic of threading anyways, heres an important thing to keep in mind; each set of group callbacks is performed on its own thread. It is thus your responsibility to take this into account. E.g. if you are planning to update a DB model with the status of a notification delivery, be sure to respect the threading rules of your DB client, which usually means to not re-use models that were loaded on a different thread.

A simple approach to this is by passing the data you need to be able to update the DB as a context, which can be any type of object or an array objects:

group.send_notification(notification, context: model.id) do |response, model_id|
  reloaded_model = Model.find(model_id)
  if response.success?
    reloaded_model.touch(:sent_at)
  else
    reloaded_model.update_attribute(:last_response, response.status)
  end
end

Connection pool

When you need to be able to deliver many notifications in a short amount of time, it can be beneficial to open multiple connections to the remote service. By default Lowdown will initialize clients with a single connection, but you may increase this with the pool_size option:

Lowdown::Client.production(true, certificate: File.read("path/to/certificate.pem"), pool_size: 3)

Connect to APNS via proxy

Lowdown::Connection#initialize accepts a lambda to build TCPSocket. Build a duck type of TCPSocket which go through proxy.

socket_maker = lambda do |uri|
  Proxifier::Proxy('http://127.0.0.1:3128').open \
    uri.host, uri.port, nil, nil, Celluloid::IO::TCPSocket
end

connection_pool = Lowdown::Connection.pool \
  size: 2,
  args: [uri, cert.ssl_context, true, socket_maker]

client = Lowdown::Client.client_with_connection connection_pool, certificate: cert

Gotchas

Forking is done by, e.g. Spring and DelayedJob, when daemonizing workers. In practice, this means that e.g. you should not initialize a client from a Rails initializer, but rather do it lazily when its really required. E.g.:

class PushNotificationService
  def initialize(certificate_path)
    @certificate_path = certificate_path
    @client_mutex = Mutex.new
  end

  def client
    client = nil
    @client_mutex.synchronize do
      @client ||= Lowdown::Client.production(true, File.read(certificate_path), keep_alive: true)
      client = @client
    end
    client
  end
end

# In your initializer:
PUSH_NOTIFICATION_SERVICE = PushNotificationService.new("path/to/certificate.pem")
  • It's reported not working with spring and Rails 4.2.5 (issue #9) even when creating Lowdown::Client instance after forking, outside Rails initializer, e.g. in Rails console.

Related tool

Also checkout this library for scheduling across time zones.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/alloy/lowdown.

License

The gem is available as open source under the terms of the MIT License.

lowdown open issues Ask a question     (View All Issues)
  • over 2 years V2
  • about 3 years Apple Certificate
lowdown open pull requests (View All Pulls)
  • CHG: update README with issue 9
  • Getting tests to pass in Jruby 9.0.5.0
  • downgrade celluloid-io
lowdown questions on Stackoverflow (View All Questions)
  • The lowdown on skills linkedin API
  • SEO on jQuery.toggle Links. Whats the lowdown?
lowdown list of languages used
Other projects in Ruby