• Stars
    star
    120
  • Rank 295,983 (Top 6 %)
  • Language
    Ruby
  • License
    MIT License
  • Created almost 9 years ago
  • Updated almost 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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 you’ll 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 you’ll 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!" })

There’s 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 you’ll 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 you’re delivering them from would normally chug along, which is often not what you’d 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 there’s 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 group’s background thread.

Threading

While we’re on the topic of threading anyways, here’s 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

  • If you’re forking your process, be sure to not load lowdown before forking (because this does not work well with Celluloid, or with threading and Ruby in general).

    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 it’s 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.

More Repositories

1

kicker

A lean, agnostic, flexible file-change watcher, using OS X FSEvents.
Ruby
556
star
2

MotionData

An experiment in using Core Data in a Ruby-ish way with RubyMotion
Objective-C
145
star
3

LLDB-Is-It-Not

Xcode plugin to load project specific .lldbinit
Objective-C
136
star
4

UitzendingGemist-ATV2

A simple Apple TV 2 application that shows content from UitzendingGemist.nl without having to use AirPlay.
Objective-C
94
star
5

AxeMode

🐒🔧 for Xcode
Objective-C
62
star
6

dietrb

IRB on a diet, for MacRuby / Ruby 1.9
Ruby
62
star
7

git-svn-mirror

A command-line tool that automates the task of creating a GIT mirror for a SVN repo, and keeping it up-to-date.
Ruby
60
star
8

MacRubyWebKitPlugInExample

A WebKit plugin written in MacRuby, adding ruby power to the web kid!
Objective-C
53
star
9

RealNative

React Native C Example – WHEN PERFORMANCE TRULY MATTERS!
C
50
star
10

watch-sim

A command-line WatchKit application launcher.
Objective-C
42
star
11

rucola

A Framework for rapidly building RubyCocoa applications
Ruby
34
star
12

clang-compilation-database-tool

Tool that can be used to generate Clang Compilation DBs from Xcode.
Objective-C
29
star
13

ruby-nzb

A simple Ruby NZB download/post-process library, with a very naive command line client. When done, a separate OSX GUI client will use this library.
Ruby
23
star
14

no-slacking-on-pull-requests-bot

A Slack bot that keeps track of open PRs.
JavaScript
19
star
15

adding-more-native-to-your-react-native-app

Objective-C
18
star
16

interactive-macruby

A MacRuby Cocoa REPL
Ruby
17
star
17

relational-theory

A prototype of server-side React+Relay rendering.
TypeScript
17
star
18

mocha-on-bacon

Because it's yummy!
Ruby
16
star
19

ForceOrientationTest

Experimenting with forcing orientation changes on iOS 6.
Objective-C
16
star
20

paper-cups

A small cozy web chat app, like paper cups and a string back in the days. Usually surrounded by lots of French chatter…
JavaScript
14
star
21

flight-seeker

Find flights and calculate level/award mileage.
Ruby
14
star
22

time_zone_scheduler

A Ruby library that assists in scheduling events whilst taking time zones into account.
Ruby
14
star
23

ObjectiveBacon

A small RSpec clone, with NSRunLoop powers. The core of MacBacon & NuBacon.
C
13
star
24

ruby-trace

Prototype of a call trace visualizer.
JavaScript
12
star
25

webapp-app

A SSB OSX application, which at some point will be able to create a new application which wraps a specific web application, (Think Campfire, Twitter etc) and allows the user to use Ruby to create event handlers to be able support things like Growl or whatever you would like.
Ruby
12
star
26

Opinions

These opinions are mine and you should make them yours too.
11
star
27

instruments

Ruby
11
star
28

vscode-relay

TypeScript
10
star
29

dotvim

Just my weak attempt at moving to VIM
Vim Script
10
star
30

NuBacon

A small RSpec clone for the Nu and Objective-C programming languages
C
10
star
31

mr-experimental

This is my private git mirror of the experimental branch (0.5), it might not always be up-to-date so keep that in mind.
Ruby
10
star
32

relay2ts

Generate TypeScript interfaces for your Relay GraphQL query fragments.
TypeScript
8
star
33

UISpec

fork of UISpec for iPhone
Objective-C
8
star
34

microgem

Toying with a clean room implementation of the rubygems ‘install’ command. Focus is on naivety for portability, MacRuby for instance.
Ruby
8
star
35

art.c

Artsy Salon 2019 art exhibition entry.
C
7
star
36

relay-only-local-state

TypeScript
7
star
37

react-native-macos

C++
7
star
38

graphqless-js

Statically compiled resolvers for entire queries based on a graphql-js schema.
TypeScript
7
star
39

react-native-eu

Slides from my talk at React Native EU 2017
6
star
40

dotfiles

My config files.
Shell
5
star
41

KISStribution

C
5
star
42

anthology

Where stories are chronicled.
Ruby
5
star
43

ios-sim-test

Ruby
4
star
44

macruby-asl-logger

A MacRuby wrapper of the Apple System Log facility
4
star
45

undercover

Undercover: CIA Ruby agent for GitHub
Ruby
4
star
46

feel-good

A simple task and time-tracker to make you feel good about yourself.
TypeScript
4
star
47

passengerpane

[REPO MOVED!] A Mac OS X preference pane for easily configuring Rails applications with Passenger.
4
star
48

rubycocoa-prefs

A ruby abstraction for read/write access to the NSUserDefaults of a Cocoa application.
Ruby
3
star
49

juse

A Jest script transformer backed FUSE implementation
TypeScript
3
star
50

railsgirls-polymorphic-associations-and-single-table-inheritance

Ruby
3
star
51

ControlTower-mirror-test

C
3
star
52

flow2dts

3
star
53

rubyenrails09

Ruby
3
star
54

MacOnRack

A sample MacRuby application demonstrating how to connect Rack to a WebView.
Ruby
3
star
55

eloyendionnetrouwen.nl

Ruby
3
star
56

libedit-merge

C
3
star
57

ssalleyware

SSL (cert verification for Ruby) Anywhere!
Ruby
3
star
58

TremoloAudioUnitTutorial

C++
3
star
59

SBTR

SHOOT BY THE RULES
3
star
60

repo_page_san_test

TODO
Ruby
2
star
61

pasternakredux

Ruby
2
star
62

alloy.github.com

Ruby
2
star
63

CPGuides

2
star
64

life

2
star
65

rails-ticket-sample-bin

A Rails application used to try out examples given in Rails tickets.
Ruby
2
star
66

MacRubyWebsite-mirror-test

Ruby
2
star
67

slides

Conf slides
2
star
68

The-Jag

Our Jaguar X-Type
2
star
69

limechat-plugin

Some random collected thoughts on a LimeChat plugin API
Ruby
2
star
70

piccsy-fetcher

Fetches low-res versions of the images you’ve ‘picked’ on piccsy.com
Ruby
2
star
71

create_github_project

From the hackpits, a setup script which creates a new project on github, adds users and enable services.
2
star
72

cocoa_gist

A simple lib which uses RubyCocoa to create a Gist. This is made for LimeChat, but is fairly general.
Ruby
2
star
73

todomvc-apollo-client-with-subscriptions

JavaScript
2
star
74

rucola-xcode

Classes to assist with XCode project related tasks
Ruby
2
star
75

mrdate

Ruby
2
star
76

microspec

A very simple test/spec like clone used for MacRuby, without the rspec assertion syntax. Especially for mon ami Laurent ;)
2
star
77

todo

2
star
78

MacRuby-mirror-test

Ruby
2
star
79

draft-issues

2
star
80

superalloy.nl

Where I write…
2
star
81

relay-rerender-example

Created with CodeSandbox
JavaScript
2
star
82

merge-schemas-enum-repro

JavaScript
1
star
83

tsc-exclude-types-repro

JavaScript
1
star
84

graphql-augment-poc

TypeScript
1
star
85

TestRNW

JavaScript
1
star
86

the-tesla

Model X 75D
1
star
87

NiNoKuni

Walkthrough version diffs.
1
star
88

homebridge-linak

TypeScript
1
star
89

TestPLCrashReporter

A RubyMotion app that I use to easily test PLCrashReporter on both iOS and OS X
Ruby
1
star
90

graphql-finland

1
star
91

rn2dts

TypeScript
1
star
92

NSHipster-ContactTracingManager

Swift
1
star
93

houseboat

🏠⛵
1
star
94

HockeySDK-CocoaPods

Objective-C
1
star
95

ReactNativeExperiments

Objective-C
1
star
96

henkenmarieketrouwen.nl

Ruby
1
star