Giter VIP home page Giter VIP logo

houston's Introduction

Houston

Travis

Note: This project is no longer being maintained. Houston supports only the legacy binary APNs protocol, which as of March 31, 2021 is no longer supported. Please migrate to an alternative library, such as Apnotic to send push notifications using the new APNs2 protocol.

Push Notifications don't have to be difficult.

Houston is a simple gem for sending Apple Push Notifications. Pass your credentials, construct your message, and send it.

In a production application, you will probably want to schedule or queue notifications into a background job. Whether you're using queue_classic, resque, or rolling you own infrastructure, integrating Houston couldn't be simpler.

Houston is named for Houston, TX, the metonymical home of NASA's Johnson Space Center, as in Houston, We Have Liftoff!.

Installation

$ gem install houston

Usage

require 'houston'

# Environment variables are automatically read, or can be overridden by any specified options. You can also
# conveniently use `Houston::Client.development` or `Houston::Client.production`.
APN = Houston::Client.development
APN.certificate = File.read('/path/to/apple_push_notification.pem')

# An example of the token sent back when a device registers for notifications
token = '<ce8be627 2e43e855 16033e24 b4c28922 0eeda487 9c477160 b2545e95 b68b5969>'

# Create a notification that alerts a message to the user, plays a sound, and sets the badge on the app
notification = Houston::Notification.new(device: token)
notification.alert = 'Hello, World!'

# Notifications can also change the badge count, have a custom sound, have a category identifier, indicate available Newsstand content, or pass along arbitrary data.
notification.badge = 57
notification.sound = 'sosumi.aiff'
notification.category = 'INVITE_CATEGORY'
notification.content_available = true
notification.mutable_content = true
notification.custom_data = { foo: 'bar' }
notification.url_args = %w[boarding A998]
notification.thread_id = 'notify-team-ios'

# And... sent! That's all it takes.
APN.push(notification)

To generate a .pem file, it is recommended to use fastlane pem, which completely automates the process of creating the certificate.

Configuration

Houston will attempt to load configuration data from environment variables, if they're present. The following variables will be used.

Environment Variable Description
APN_GATEWAY_URI The base URI for the APNS service to use. If left blank, will use the default APNS Production Gateway URI.
APN_FEEDBACK_URI The base URI for the APNS feedback service to use. If left blank, will use the default APNS Production Feedback URI.
APN_CERTIFICATE The file path to a valid APNS push certificate in .pem format (see "Converting Your Certificate" below).
APN_CERTIFICATE_DATA The contents of a valid APNS push certificate in .pem format (see "Converting Your Certificate" below); used in lieu of APN_CERTIFICATE if that variable is not provided.
APN_CERTIFICATE_PASSPHRASE If the APNS certificate is protected by a passphrase, provide this variable to use when decrypting it.
APN_TIMEOUT The timeout used when communicating with APNS. If not provided, the default of 0.5 seconds is used.

Error Handling

If an error occurs when sending a particular notification, its error attribute will be populated:

puts "Error: #{notification.error}." if notification.error

Silent Notifications

To send a silent push notification, set sound to an empty string (''):

Houston::Notification.new(sound: '', content_available: true)

Mutable Notifications

To send a mutable push notification (supported by iOS 10+), set mutable_content to true:

Houston::Notification.new(mutable_content: true)

Persistent Connections

If you want to manage your own persistent connection to Apple push services, such as for background workers, here's how to do it:

certificate = File.read('/path/to/apple_push_notification.pem')
passphrase = '...'
connection = Houston::Connection.new(Houston::APPLE_DEVELOPMENT_GATEWAY_URI, certificate, passphrase)
connection.open

notification = Houston::Notification.new(device: token)
notification.alert = 'Hello, World!'
connection.write(notification.message)

connection.close

Feedback Service

Apple provides a feedback service to query for unregistered device tokens, these are devices that have failed to receive a push notification and should be removed from your application. You should periodically query for and remove these devices, Apple audits providers to ensure they are removing unregistered devices. To obtain the list of unregistered device tokens:

Houston::Client.development.devices

In practice, you'll use a reference to instance of the APN object you create (see the Usage section). Here's a rake job that marks device tokens as invalid based on the feedback service from Apple. This example assumes devices are tracked in a model called Device (i.e. User.devices).

In lib/tasks/notifications.rake:

namespace :notifications do
  task device_token_feedback: [:environment] do
    APN.unregistered_devices.each do |device_hash|
      # Format: { token: token, timestamp: timestamp }
      device = Device.find_by(token: device_hash[:token])
      next unless device.present?
      # Remove device token
      device.update_attribute(:device_token, nil)
    end
  end
end

Versioning

Houston 2.0 supports the new enhanced notification format. Support for the legacy notification format is available in 1.x releases.

Command Line Tool

Houston also comes with the apn binary, which provides a convenient way to test notifications from the command line.

$ apn push "<token>" -c /path/to/apple_push_notification.pem -m "Hello from the command line! "

Enabling Push Notifications on iOS

Objective-C

// AppDelegate.m

- (void)applicationDidFinishLaunching:(UIApplication *)application {
  // ...

  [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}

- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    NSLog(@"application:didRegisterForRemoteNotificationsWithDeviceToken: %@", deviceToken);

    // Register the device token with a webservice
}

- (void)application:(UIApplication *)application
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    NSLog(@"Error: %@", error);
}

Swift

// AppDelegate.swift

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
    application.registerForRemoteNotifications()
    return true
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    // Register the device token with a webservice
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    print("error \(error)")
}

Converting Your Certificate

These instructions come from the APN on Rails project, which is another great option for sending push notifications.

Once you have the certificate from Apple for your application, export your key and the apple certificate as p12 files. Here is a quick walkthrough on how to do this:

  1. Click the disclosure arrow next to your certificate in Keychain Access and select the certificate and the key.
  2. Right click and choose Export 2 items….
  3. Choose the p12 format from the drop down and name it cert.p12.

Now convert the p12 file to a pem file:

Unencrypted private key (do not store this certificate in version control)

$ openssl pkcs12 -in cert.p12 -out apple_push_notification.pem -nodes -clcerts

Encrypted private key

$ openssl pkcs12 -in cert.p12 -out apple_push_notification.pem -aes256 -clcerts

You'll have to specify the password in the APN_CERTIFICATE_PASSPHRASE environment variable if you use the encrypted option.

License

Houston is available under the MIT license. See the LICENSE file for more info.

houston's People

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  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

houston's Issues

Houston::Connection.open opens and closes socket with each call

In connection.rb, Houston::Connection.open opens and closes a TCP connection to Apple's servers each time it is called.

However, page 47 of Apple's Local and Push Notification Programming Guide states the following:

You should also retain connections with APNs across multiple notifications. APNs may consider connections that are rapidly and repeatedly established and torn down as a denial of-service attack.

Would it be better for Houston::Connection.open to cache connections been invocations? This approach is permitted by Apple:

You may establish multiple, parallel connections to the same gateway or to multiple gateway instances.

CLI Tool

It would be cool if this gem had a CLI (similar to venice) that would do the .p12 -> .pem conversion for you. I have to come to the houston readme and copy the stupid command out every time I setup push notifications.

No result after sending a push

EDIT: Using Houston::Client.production has no affect. puts notification.error prints out nothing.

Following tutorial, nothing useful is logged or returned. My pushes aren't arriving.

I've tried removing spaces and <> too.

Using rspec 3.1.0
Using simplecov-html 0.8.0
require 'houston'

# Environment variables are automatically read, or can be overridden by any specified options. You can also
# conveniently use `Houston::Client.development` or `Houston::Client.production`.
APN = Houston::Client.development
APN.certificate = File.read("/Users/quantum/Documents/cliqupprodcerts.pem")


# An example of the token sent back when a device registers for notifications

token = "tokentoken"
# Create a notification that alerts a message to the user, plays a sound, and sets the badge on the app
notification = Houston::Notification.new(device: token)
notification.alert = "Hello, World!"

# Notifications can also change the badge count, have a custom sound, have a category identifier, indicate available Newsstand content, or pass along arbitrary data.
notification.badge = 57
notification.sound = "sosumi.aiff"
notification.category = "INVITE_CATEGORY"
notification.content_available = true
notification.custom_data = {foo: "bar"}

# And... sent! That's all it takes.
v = APN.push(notification)
puts v
~

Why does Client#push accept a splat instead of an Enumerable type?

For awhile, I thought Client#push accepted an Enumerable object, so I started passing in a Set of notifications which caused the method to silently fail. I didn't do a complete investigation, but it seems like the call to notifications.last failed and the exception was probably eaten up by the Connection block. While trying to get to the bottom of this, I realized the method takes a splat not an Enumerable. Was able to resolve my issue by adjusting my calling code accordingly. It's very possible this is the "right" way to do things, but what is the reasoning (for my own edification)? Also, it would be nice if the method either succeeded or didn't fail silently when passed a Set. Maybe it's as simple as changing this:

break if notifications.count == 1 || notification == notifications.last

to

break if index == (notification.count - 1)

?

setting env var as certificate doesn't work

This might be an issue of better documentation, but as a first time user of houston and push notifications, I had the following problem:

I used dotenv to set the environment variable:

APN_CERTIFICATE='/Users/mehulkar/dev/myproj/api/cert.pem'

in an Rails initializer, I create the client, assuming that the env vars will be used automatically.

APN = Houston::Client.new

Now, when I open up rails console and try to send a push, I get this error.

notification = Houston::Notification.new(token: 'foo')
APN.push(notification)
#=> OpenSSL::PKey::RSAError: Neither PUB key nor PRIV key: not enough data

Looking at the code, this seems obvious to me because Houston::Client.development, for example, sets the certificate accessor to the contents of the file, rather than the path

When I reassign APN.certificate=File.read(APN.certificate), then it works.

There are a few of solutions:

  1. Document that this reassignment needs to happen
  2. Use a different ruby accessor like certificate_path and on initialize, attempt to read that if @certificate is nil
  3. auto reassign APN.certificate if FIle.exists? APN.certificate is true either in Client#push or in Client#new

Let me know if I've missed something obvious or if I'm completely wrong here.

Monitoring APNs error responses

I am looking to build a logging system for APNs errors that houston may receive when sending notifications.

For example, in the Apple documentation table 5.1 useful response error codes are listed that can be helpful when debugging issues when a connection is established but the notification is malformed in some way.

The node-apn library, for example, defines these error codes and allows clients to attach callbacks to an event emitter so that errors can be logged (or otherwise responded to in some way).

Does such a thing exist in houston? I've looked through the source but it seems that clients may not have a facility to monitor the lower level feedback that houston gets from the APNs socket connection.

Thanks for any advice you might have!

missing 0.3.1

The version of this gem on rubygems is 0.3.1, but that version is missing from this repository. It is possibly just a matter of the tag not being pushed.

Connection to APN service dying with multiple notifications to send

When I try to send a notification I'm seeing the following error:

Errno::EPIPE (Broken pipe)

I don't get a good stack trace, but through some logging I've managed to find that it's happening in Client#push in the loop that actually calls Connection#write. It does get through a few notifications (devices) before dying, usually somewhere between 20-50 out of 116. Even in the iteration where the exception occurs, Connection#open? still returns true. I don't know Ruby sockets very well, so I'm not sure where to go from here.

readme is vague concerning key

Once you have the certificate from Apple for your application, export your key and the apple certificate as p12 files.

Should it be the public key or private key?

Detect environment from .pem file in CLI

I initially thought that the CLI automatically picked development or production push servers depending on the information in the .pem file. This would be great since it's such an easy mistake to make when testing production certificates. The CLI even responded successfully when using a production SSL certificate on the development servers.

Persisted connections are not re-connecting in case they are dropped

This is not entirely Houston's fault, but the way Ruby Sockets work. When the server looses connection to Apple gateways, Houston knows nothing about it and connection#open? will still return true. Moreover, you seem to be able to write to such dead connection, yet nothing is delivered to Apple.

Underlying TCPConnection that is being opened to Apple is not working properly, yet we know nothing about it.

I have too little experience with Ruby TCP programming to solve it myself. But if someone can point me to right direction I could create a patch and pr.

Any ideas how to solve this?

Invalid tokens, IO.select and unsent Notifications

We're evaluating houston after inheriting a project that heavily uses grocer.

One of the persistent problems the project had with grocer was that failed notifications caused persistent connections to drop subsequent notifications, silently.

Pardon me if I'm retreading old ground here, but for reference, see grocer/grocer#14 for discussion around grocer and this topic. In a nutshell, the APNS architecture notifies of failures on the push socket, but not of successes, so you're required to either block the connection until the failure notification appears, or a timeout has been exceeded. The APNS feedback service can be used to determine, after the fact, which device tokens were bad, but it will not indicate which messages have failed, so re-sending subsequent failures is complex.

It looks like the changes around e71b87d address this issue by implementing the IO.select solution mentioned in the above issue, and implemented in a few forks (https://github.com/ping4/grocer/blob/ping4/lib/grocer/ssl_connection.rb has an example).

I'm new to IO.select, but as far as I can tell, this;

ssl = connection.ssl
...
read_socket, write_socket = IO.select([ssl], [ssl], [ssl], nil)

will block until either read_socket is ready for reading or write_socket is ready for writing. So it's possible that IO.select will return before the APNS has pushed an error code, indicating a false positive. In informal testing, this seems to be exactly what happens:

    note = Houston::Notification.new(token: "8762d160 3dad3f1b ccd84855 7576bc66 784b9a61 oh who am I kidding I'm totally invalid", alert: "You'll never see me")

    connection.write note.message
    ssl = connection.ssl

    read_socket, write_socket = IO.select([ssl], [ssl], [ssl], nil)
    if read_socket && read_socket[0]
      response = connection.read(6).unpack("ccN")
      puts "Response: 2 #{response}"
    end

The error never appears: IO.select returned because write_socket became ready for writing, not because read_socket became ready for reading.

Without the second argument, IO.select will block (forever) until the read_socket is ready with an error to read. It does, however, reliably contain error messages for failed device_tokens.

Without the second argument, and some small-ish value for timeout (the fourth arg) accomplishes both goals, at the expense of waiting for timeout seconds to pass before the next message can send:

read_socket, write_socket = IO.select([ssl], [], [ssl], 0.1)

The timeout tax might work in certain situations, so I'm not willing to discard it.

That said, I'm already out of my depth. My limited research indicates that this is the accepted solution area, but grocer has mostly abandoned this idea, and nobody is happy about the blocking. Is there a solution to this that doesn't involve opening and closing connections repeatedly?

Silent failure due to environment/certificate incompatibility

I've fixed this issue, but I reckon it might be good to leave a note of it here:

I was getting silent failures when sending push notifications -- the notification object, after calling push(), had no errors on it, but nothing was being sent out. After a bit of digging, I noticed that it was because I had the client set as Houston::Client.production (to match the Rails environment), where the certificate file was not for a production environment. Switching over to Houston::Client.development fixed the problem.

I know this isn't Houston's issue, per se, but I did notice that folks were having problems with silent failures. This might be a lead for some of you. I don't even know if it's possible for Houston to look at the certificate and determine its proper environment, then alert me of a mismatch, but I hope this helps people in the interim.

Test suite broken

This merge pull request #32 for silent push broke the test suite. See the comment at the bottom of the pull request for a possible resolve.

Push Notifications reported as sent and without error but not received in case of ~ 20 notifications

In case we send a certain amount of push notifications, they don’t seem to be delivered correctly. Everything works correctly for a lower amount of push notifications. We do not use persistent connections.

Here’s some very rough logging code that we had in place:

    client.push(*notifications)
    Rails.logger.info "PNS Job (1/2): Send #{notifications.length} notifications. Notification Sample: #{notifications.first.inspect}. Errors: #{notifications.collect(&:error).compact}. Sent: #{notifications.collect(&:sent?)}"
    Rails.logger.info "PNS Job (2/2): #{client.devices}, Push Infos: #{push_options.inspect}"

All following examples were created with the production build of the app, using the production certificate and Houston production client with houston 2.2.1.

Here’s the log output of a working example. As you can see, the errors are empty and both notifications are reported as being sent, and they are correctly received on both clients.

PNS Job (1/2): Send 2 notifications. Notification Sample: #<Houston::Notification:0x000000070bb240 @token="<REPLACED>", @alert="Some Text", @badge=1, @sound=nil, @category=nil, @expiry=nil, @id=0, @priority=nil, @content_available=true, @custom_data={:custom_data=>{:rscs=>[{REPLACED}]}}, @sent_at=2014-09-26 10:15:13 +0200>.
Errors: []. Sent: [true, true]
PNS Job (2/2): [], Push Infos: {:alert=>"Some Text", :badge=>1, :content_available=>true, :custom_data=>{:rscs=>[{REPLACED}]}}

Here’s the log output of an example that does not work. Again, the errors are empty and all notifications are reported as being sent, but they are not in fact received on the target devices. The target devices include the two devices from the previous working example:

PNS Job (1/2): Send 23 notifications. Notification Sample: #<Houston::Notification:0x0000000c8dc488 @token="<REPLACED>", @alert="Some Alert", @badge=1, @sound=nil, @category=nil, @expiry=nil, @id=0, @priority=nil, @content_available=true, @custom_data={:custom_data=>{:rscs=>[{REPLACED}]}}, @sent_at=2014-09-29 15:33:36 +0200>.
Errors: []. Sent: [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]
PNS Job (2/2): [], Push Infos: {:alert=>"Some Alert", :badge=>1, :content_available=>true, :custom_data=>{:rscs=>[{REPLACED}]}}

What could be the source of the error or what else could we look into to narrow down the problem?

Command line tool works, but not gem

I can't get Houston to work in my Rails app.

I initialize it in a houston.rb file in /config/initializers/ with the below code:

APN = Houston::Client.production
APN.certificate = File.read("config/apple_push_notification_production.pem")

After that I use it as shown in the docs:

notification = Houston::Notification.new(device: token)
notification.alert = "Hello, World!"
APN.push(notification)

The gem seems properly initialized as typing APN in the Rails console outputs data like @gateway_uri @certificate etc..

Using the command line tool, the same token, and the same key everything works just fine. What could be the issue?

iPhone ringing for not cleared notifications

Hi @mattt
I have an issue with the gem when:

  • I send a notification "1" to a device
  • I don't read the notification on my device
  • I send another notification "2" to the same device
  • the application rings twice

When the device is not locked, it displayed the old notification as a banner first, then the new one.
Somehow old notifications are still triggering the ringtone and the notification center to display it again... any idea what is the issue?

How can I store token in server?

I made a certificate and got token. And push worked very well.
But I don't know how can I send this token to the server.
And houston, how can load this tokens? I google this but no one post it.
Please explain this. Thanks.

Remote notification doesn't arrive on production server, but does on sandbox.

Problem Description

I'm ready for my code to switch from the Sandbox APN server to the production server. I generated a production certificate and can successfully connect to the APN server with it. However when I try to manually send a notification it never arrives on the device, even though Houston is telling me that it was pushed.

I open my app in the release scheme and grab the APN token given to me by apple. I convert it into a NSString. Then, using the certificate I generated and tested, I run this command:

apn push "<apnstoken>" -c ck.pem -m "THIS BETTER WORK" -p

I type in my password and I see: 1 push notification sent successfully but I never actually see any notification on my device. This happens both with the CLI, and in code using the ruby gem:

apn = Houston::Client.production
apn.certificate = File.read("<pathtocert>")
apn.passphrase = '<password>'

# Create a notification that alerts a message to the user, plays a sound, and sets the badge on the app
notification = Houston::Notification.new(device: apntoken)
notification.alert = body
notification.custom_data = options

# Notifications can also change the badge count, have a custom sound, have a category identifier, indicate available Newsstand content, or pass along arbitrary data.
notification.content_available = true

# And... sent! That's all it takes.
apn.push(notification)

I'm used the same process for creating the cert as I did with the sandbox -- except I chose Production cert and not development.
cert

And the code I'm using is the same with the updated password and change to Client.production.

Expected output

The notification arrive on my device

Actual output

A successful response, but no notification

Environmental information

  • ruby 2.0.0p353 (2013-11-22) [x86_64-linux]
  • Linux 3.10.0-123.8.1.el7.x86_64 #1 SMP Mon Sep 22 19:06:58 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux (CentOS 7)

Push is "successful" if token is invalid

The apn command line reports success even if the token is invalid, say, due to missing characters (easily reproducible by deleting the last character from a token).

Cant get any feedback or any error even tho there is a problem

using houston (2.2.1) ruby 2.1.2 and rails 4.1.4 i m sending push notification to the users but even tho i entered false device token it doesn't raise any notification error, and overtime when i try to use Feedback service it gives me an empty array.

notification = Houston::Notification.new(device: User.last.device_token)
notification.alert = "Feedback test!"
connection.write(notification.message)
puts "Error: #{notification.error}." if notification.error

is it some kind of bug anything wrong in my side,

I can send the other notifications without a problem but i need to know if its not sent and the reason (the 10 feedback code of apple provides)

Number in a payload delivered as NSString

When I send a notification like this

apn push fcc3551cd74e40d45beb109946a6ca6dc7423e16dd042e2b2aa06f07183d5b8c -c apns-dev-cert.pem -m "Credits" -d credits=100

payload's credits: value is delivered as instance of NSString but not a NSNumber

Invalid Maximum Payload Size

Is the Houston::Notification::APNSError::MAXIMUM_PAYLOAD_SIZE of 2048 bytes invalid?

According to this issue, in production the maximum payload size APNS will accept is still 256 bytes.

Houston::Client.production.devices raises exception

I get the following error when I call Houston::Client.production.devices from the rails console.

[2] pry(main)> Houston::Client.production.devices
TypeError: no implicit conversion of nil into String
from /app/vendor/bundle/ruby/2.2.0/gems/houston-2.2.3/lib/houston/connection.rb:39:in `initialize'

Passphrase

Looking through the sample code in the Github page, where would I enter my passphrase in the code?

Support for PKCS#12 format and/or Keychain certificates

When you create an APNs certificate, you usually add it to the Keychain. Keychain only supports exporting to PKCS#12 format, which makes it necessary to convert the certificate to .PEM format using OpenSSL. This is an annoying step and according to OpenSSL::PKCS12 Ruby already supports PKCS12 certificates. This could be added to get rid of the conversion step.

In addition, through using Security tool we could also support the certificates in Keychain.

Are notifications retried indefinitely and without delay?

According to the following lines at the end of client.rb's push method:

  if error
    command, status, index = error.unpack("ccN")
    notifications.slice!(0..index)
    notifications.each(&:mark_as_unsent!)
    push(*notifications)
  end

it seems that the push method is called recursively until no error is returned from the APN server. Am I correct in this?

If so, isn't this a terrible idea? The APNs may respond with something as simple as "invalid token". Shouldn't there at least be a maximum retry amount?

Houston::Client.development.devices fails if no passphrase is given

If Houston is initialized without a passphrase, e.g.:

APN = Houston::Client.development
APN.certificate = File.read("#{Rails.root}/config/aps_development.pem")

Then the call to

Houston::Client.development.devices

fails in connection.rb:39, because @passphrase is nil.

It is incorrect to use < token > string in the Houston API

It is incorrect to use < token > string in the Houston API because this is just a string representation of NSData used for debugging. The correct token should look like a simple hex string with no spaces or angle brackets. This is how it is stored in pretty much every implementation. The way Houston does it makes the API very awkward and inconvenient.

Using houston within a rails app

This is quite a noob question as I'm still pretty new to rails. I'm wondering where's the right place to do the basic setup for houston? i.e. if I want to send a push notification in a controller, where is the right place to set up the APN code below:

require 'houston'
APN = Houston::Client.development
APN.certificate = File.read("/path/to/apple_push_notification.pem")

Do I do it directly in that controller file? That seems wrong... Do I do it in an initializer file? Is it ok to simply just create an arbitrary file within the initializers directory? Thanks a lot!

Pushing multiple message objects

If i want to send more than one messages is the expected call supposed to be this?

apn_client.push(message1,message2,message3)

I would expect it would be more natural to call

apn_client.push(messages_array)

The method inside should be able to handle one or many messages.

At the moment i have to call it as

apn_client.push(*message_array)

What do you think?

Some emojis are broken

I cannot send some emojis.

☕ (\xE2\x98\x95), ☝️ (\xE2\x98\x9D) are fine but 🎮 (\xF0\x9F\x8E\xAE), 🍕 (\xF0\x9F\x8D\x95) are broken.

CLI -n not setting content-available flag

If I use the CLI tool with the -n flag, it isn't sending content-available in the body. Setting the content_available attribute in a ruby script works. Is anyone else seeing this?

Houston and Sidekiq and SSL problems

Hi there!
I've been using Houston for awhile and it is awesome for sending out APNS. My rails server is currently on Heroku and I am sending notifications via Houston in the background with Sidekiq. The problem is every hour, I get SSL failure and no more notifications can be fired. What I'm seeing in the logs looks like:

2014-04-09T18:31:03.254876+00:00 app[worker.1]: 2014-04-09T18:31:03Z 2 TID-ov652jh60 NotificationWorker JID-6e547188b78402ed2702f70d INFO: fail: 0.009 sec
2014-04-09T18:31:03.261789+00:00 app[worker.1]: 2014-04-09T18:31:03Z 2 TID-ov652jh60 WARN: {"retry"=>true, "queue"=>"default", "unique"=>true, "class"=>"NotificationWorker", "args"=>[561, "a0233ebdda747002e2b71113bf26adcd8a9188005113c24a4b50ad3098044a41"], "jid"=>"6e547188b78402ed2702f70d", "enqueued_at"=>1397066881.4699736, "error_message"=>"SSL_write: bad write retry", "error_class"=>"OpenSSL::SSL::SSLError", "failed_at"=>1397066881.4908812, "retry_count"=>6, "retried_at"=>1397068263.2529194}
2014-04-09T18:31:03.261843+00:00 app[worker.1]: 2014-04-09T18:31:03Z 2 TID-ov652jh60 WARN: SSL_write: bad write retry
2014-04-09T18:31:03.261929+00:00 app[worker.1]: 2014-04-09T18:31:03Z 2 TID-ov652jh60 WARN: /app/vendor/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:326:in `syswrite'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/vendor/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:326:in `do_write'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/vendor/ruby-2.1.1/lib/ruby/2.1.0/openssl/buffering.rb:344:in `write'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/app/wo
rkers/notification_worker.rb:43:in `block in perform'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/connection_pool-2.0.0/lib/connection_pool.rb:58:in `with'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/app/workers/notification_worker.rb:27:in `perform'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/processor.rb:50:in `block (2 levels) in process'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/chain.rb:122:in `call'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/chain.rb:122:in `block in invoke'
2014-04-09T18:31:03.261929+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/newrelic_rpm-3.7.3.204/lib/new_relic/agent/instrumentation/sidekiq.rb:30:in `block in call'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/newrelic_rpm-3.7.3.204/lib/new_relic/agent/instrumentation/controller_instrumentation.rb:335:in `perform_action_with_newrelic_trace'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/newrelic_rpm-3.7.3.204/lib/new_relic/agent/instrumentation/sidekiq.rb:21:in `call'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/server/active_record.rb:6:in `call'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/server/retry_jobs.rb:62:in `call'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/server/logging.rb:11:in `block in call'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/logging.rb:22:in `with_context'
2014-04-09T18:31:03.262153+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/server/logging.rb:7:in `call'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/chain.rb:127:in `call'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/middleware/chain.rb:127:in `invoke'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/processor.rb:49:in `block in process'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/processor.rb:92:in `stats'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/sidekiq-3.0.0/lib/sidekiq/processor.rb:48:in `process'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `public_send'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `dispatch'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:122:in `dispatch'
2014-04-09T18:31:03.262354+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:322:in `block in handle_message'
2014-04-09T18:31:03.262922+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:416:in `block in task'
2014-04-09T18:31:03.262922+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks.rb:55:in `block in initialize'
2014-04-09T18:31:03.262922+00:00 app[worker.1]: /app/vendor/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks/task_fiber.rb:13:in `block in create'

My NotificationWorker file looks like

class NotificationWorker
    include Sidekiq::Worker
  sidekiq_options unique: true
    require 'houston'


    APN_POOL = ConnectionPool.new(:size => 2, :timeout => 300) do
    uri, certificate = if Rails.env.development?
      [
        Houston::APPLE_DEVELOPMENT_GATEWAY_URI,
        File.read(ENV["APN_CERT"])
      ]
    else
      [
        Houston::APPLE_PRODUCTION_GATEWAY_URI,
        File.read(ENV["APN_CERT"])
      ]
    end

    connection = Houston::Connection.new(uri, certificate, "password")
    connection.open

    connection
  end

  def perform(notification_id, token)
    APN_POOL.with do |connection|
      notification = Notification.find(notification_id)

      remote_notification = Houston::Notification.new(device: token)
      remote_notification.alert = notification.body
      remote_notification.badge = 1
      connection.write(remote_notification.message)
    end
  end
end

My Procfile looks like:

worker: bundle exec sidekiq

I have other workers running but I've limited the problem down to this notification_worker class. Has anyone else run into this problem before also?

Thanks a lot!
Anderthan

SSL certificate revoked

On monday the command line binary and even the script worked like a charm. But today I run the same script and tried the same command in terminal but both failed with this error

Exception sending notification: SSL_connect returned=1 errno=0 state=SSLv3 read server session ticket A:
sslv3 alert certificate revoked

Is this problem on my side or not?

Gemfile Installation Issue

Preface, this may have nothing to do with Houston and a lot to do with my lack of understanding of specifying how to install gems.

My Gemfile contains this:
gem 'houston', '0.1.0', :git => 'git://github.com/mattt/houston.git'

When I deploy the app to our EC2 instance, it tries to install the gem and returns this error:
/usr/local/ruby/1.9.3/lib64/ruby/1.9.1/rubygems/installer.rb:365:in `initialize': No such file or directory - /app/www/ip2/shared/bundle/ruby/1.9.1/bundler/gems/gems/houston-0.1.0/bin/apn (Errno::ENOENT)

I find the gem installed here:
/app/www/ip2/shared/bundle/ruby/1.9.1/bundler/gems/houston-3737539fb20d/bin/apn

Whats with the weird bundler/gems/gems path and how do I make sure that the Rakefile gets the correct Houton::Version?

Naming: device_token instead of device

Hello Mattt,

what do you think about changing the name of the device variable/parameter in the notification class to device_token? I think this would make it a bit clearer, especially in the context of using houston along with rack-push-notification. I just tripped over this, passing a device instance into the notification initializer, instead of giving it the device.token (which works, but will fail silently later on)

It's not a very big deal but imho this would make the notifications interface clearer. I know this would be a breaking change, but I think it's worth considering, because even without the rack-push-notification context this could make it more intuitive.

Cheers!

How to send dictionary object in alert body

Hi, I want have a test with Localized Formatted Strings with APNS. But it seems that I can only send string object in alert body.
How to send dictionary such like

"alert" : {
    "loc-key" : "GAME_PLAY_REQUEST_FORMAT",
    "loc-args" : [ "Jenna", "Frank"]
}

Thanks a lot.

No tests or way to check deliveries?

It would be nice to have houston test its own internals.

When running my own tests, it would be even better would be to add a push notifications to an internal deliveries queue. That way, as an app developer, I do not need to stub calls to Houston, to prevent a connection to Apple services.

I think ActionMailer::Base.deliveries is a good example of how one could implement this.

push to wrong environment still shown as successful

  apn push "<3fcdf78e 8564fcc9 91a7e5e4 3ba1e094 04c47081 9a2b7f89 f21913e5 e3864505>" -c apple_push_notification.pem -m "Hello world!" -e production

is working, with complete round trip to apns server and the device.

   Push notifications successfully sent

when switching the environment:

   apn push "<3fcdf78e 8564fcc9 91a7e5e4 3ba1e094 04c47081 9a2b7f89 f21913e5 e3864505>" -c apple_push_notification.pem -m "Hello world!" -e development 

I still get

   Push notifications successfully sent

Notifications sent via persistent connection don't arrive

I struggled with this issue for a long time, and finally discovered what was happening. I'm using version 2.2.1 of the gem.

I found that I could send notifications via the command line apn tool, but it was failing to send when integrating Houston into my code. After trying all sorts of different combinations I cut the code down until it was as simple as this:

apn = Houston::Client.development
apn.certificate = File.read("/path/to/apple_push_notification.pem")
token = "<ce8be627 2e43e855 16033e24 b4c28922 0eeda487 9c477160 b2545e95 b68b5969>"

notification = Houston::Notification.new(device: token)
notification.alert = "Hello, World!"

apn.push(notification)

I.e., just set a message and send. The notification would never arrive, and there was no error in notification.error. As a last attempt, I tried the Silent Notifications code in the README, meaning that I created a Notification with Houston::Notification.new(token: token, sound: ''), and it finally sent!

It seems like there's an issue with the default sound option. Maybe setting it to blank is the safest default?

Edit

Actually, I discovered in the end that it was down to invalid tokens in the database. I had registered some tokens from the iOS simulator, and in my Houston code (using the persistent connection format) was looping through all the devices sending out notifications, e.g.:

Device.each do |device|
  notification = Houston::Notification.new(device: device.token)
  notification.alert = "Hello, world"
  connection.write(notification.message)
end

It seems that all notifications sent after the one to the simulator would never arrive. After deleting the offending tokens from the database, the notifications would go through.

Is this an issue with the APN service? It seems like a bit of an issue though, as invalid tokens bring the whole thing to a halt. I don't know if you have any thoughts on this?

Thanks

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.