{"id":15048316,"url":"https://github.com/github/grocer","last_synced_at":"2025-10-04T10:30:44.425Z","repository":{"id":21043375,"uuid":"24341833","full_name":"github/grocer","owner":"github","description":"Pushing your Apple notifications since 2012.","archived":true,"fork":true,"pushed_at":"2014-09-22T19:19:17.000Z","size":351,"stargazers_count":10,"open_issues_count":0,"forks_count":8,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-09-30T00:41:54.260Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://github.com/grocer/grocer","language":null,"has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"kbrock/grocer","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/github.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-09-22T19:12:34.000Z","updated_at":"2024-07-31T03:22:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/github/grocer","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fgrocer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fgrocer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fgrocer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fgrocer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/github","download_url":"https://codeload.github.com/github/grocer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235238019,"owners_count":18958034,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T21:10:42.532Z","updated_at":"2025-10-04T10:30:44.066Z","avatar_url":"https://github.com/github.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Grocer\n\n[![Build Status](https://api.travis-ci.org/grocer/grocer.png?branch=master)](https://travis-ci.org/grocer/grocer)\n[![Dependency Status](https://gemnasium.com/grocer/grocer.png)](https://gemnasium.com/grocer/grocer)\n[![Code Climate](https://codeclimate.com/github/grocer/grocer.png)](https://codeclimate.com/github/grocer/grocer)\n\n**grocer** interfaces with the [Apple Push Notification\nService](http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html)\nto send push notifications to iOS devices.\n\nThere are other gems out there to do this, but **grocer** plans to be the\ncleanest, most extensible, and friendliest.\n\n## Requirements\n\n* Ruby/MRI 1.9.x, JRuby 1.7.x in 1.9 mode, Rubinius in 1.9 mode\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'grocer'\n```\n\nIf you are using JRuby, you will also need to add this to enable full OpenSSL support:\n\n```ruby\ngem 'jruby-openssl'\n```\n\n## Usage\n\n### Connecting\n\n```ruby\n# `certificate` is the only required option; the rest will default to the values\n# shown here.\n#\n# Information on obtaining a `.pem` file for use with `certificate` is shown\n# later.\npusher = Grocer.pusher(\n  certificate: \"/path/to/cert.pem\",      # required\n  passphrase:  \"\",                       # optional\n  gateway:     \"gateway.push.apple.com\", # optional; See note below.\n  port:        2195,                     # optional\n  retries:     3                         # optional\n)\n```\n\n#### Notes\n\n* `certificate`: If you don't have the certificate stored in a file, you\n  can pass any object that responds to `read`.\n  Example: `certificate: StringIO.new(pem_string)`\n* `gateway`: Defaults to different values depending on the `RAILS_ENV` or\n  `RACK_ENV` environment variables. If set to `production`, defaults to\n  `gateway.push.apple.com`, if set to `test`, defaults to `localhost` (see\n  [Acceptance Testing](#acceptance-testing) later), otherwise defaults to\n  `gateway.sandbox.push.apple.com`.\n* `retries`: The number of times **grocer** will retry writing to or reading\n  from the Apple Push Notification Service before raising any errors to client\n  code.\n\n### Sending Notifications\n\n```ruby\n# `device_token` and either `alert` or `badge` are required.\n#\n# Information on obtaining `device_token` is shown later.\nnotification = Grocer::Notification.new(\n  device_token: \"fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2\",\n  alert:        \"Hello from Grocer!\",\n  badge:        42,\n  sound:        \"siren.aiff\",         # optional\n  expiry:       Time.now + 60*60,     # optional; 0 is default, meaning the message is not stored\n  identifier:   1234                  # optional\n)\n\npusher.push(notification)\n```\n\nIt is desirable to reuse the same connection to send multiple notifications, as\nis recommended by Apple.\n\n```ruby\npusher = Grocer.pusher(connection_options)\nnotifications.each do |notification|\n  pusher.push(notification)\nend\n```\n\n#### Custom Payloads\n\nThe Apple documentation says \"Providers can specify custom payload values\noutside the Apple-reserved aps namespace.\" To specify a custom payload, set\n`Grocer::Notification#custom`.\n\n```ruby\nnotification = Grocer::Notification.new(\n  device_token: \"...\",\n  alert:        \"Hello from Grocer\",\n  custom: {\n    \"acme2\": [\"bang\", \"whiz\"]\n  }\n)\n\n# Generates a JSON payload like:\n# {\"aps\": {\"alert\": \"Hello from Grocer\"}, \"acme2\": [\"bang\", \"whiz\"]}\n```\n\n#### Passbook Notifications\n\nA `Grocer::PassbookNotification` is a specialized kind of notification which\ndoes not require any payload. That is, you need not (and *[Apple explicitly says\nnot to](http://developer.apple.com/library/ios/#Documentation/UserExperience/Conceptual/PassKit_PG/Chapters/Updating.html#//apple_ref/doc/uid/TP40012195-CH5-SW1)*)\nsend any payload for a Passbook notification. If you do, it will be ignored.\n\n```ruby\nnotification = Grocer::PassbookNotification.new(device_token: \"...\")\n# Generates a JSON payload like:\n# {\"aps\": {}}\n```\n\n#### Newsstand Notifications\n\nGrocer also supports the special Newsstand 'content-available' notification. `Grocer::NewsstandNotification` can be\nused for this. Like `Grocer::PassbookNotification`, it is a specialized kind of notification which does not require\nany payload. Likewise, anything you add to it will be ignored.\n\n```ruby\nnotification = Grocer::NewsstandNotification.new(device_token: \"...\")\n# Generates a JSON payload like:\n# {\"aps\": {\"content-available\":1}}\n````\n\n### Feedback\n\n```ruby\n# `certificate` is the only required option; the rest will default to the values\n# shown here.\nfeedback = Grocer.feedback(\n  certificate: \"/path/to/cert.pem\",       # required\n  passphrase:  \"\",                        # optional\n  gateway:     \"feedback.push.apple.com\", # optional; See note below.\n  port:        2196                       # optional\n  retries:     3                          # optional\n)\n\nfeedback.each do |attempt|\n  puts \"Device #{attempt.device_token} failed at #{attempt.timestamp}\"\nend\n```\n\n#### Notes\n\n* `gateway`: Defaults to `feedback.push.apple.com` **only** when running in a\n  production environment, as determined by either the `RAILS_ENV` or\n  `RACK_ENV` environment variables. In all other cases, it defaults to the\n  sandbox gateway, `feedback.sandbox.push.apple.com`.\n* `retries`: The number of times **grocer** will retry writing to or reading\n  from the Apple Push Notification Service before raising any errors to client\n  code.\n\n### Acceptance Testing\n\nGrocer ships with framework to setup a real looking APNS server. It listens on\na real SSL-capable socket bound to localhost.\n\nYou can setup an APNS client to talk to it, then inspect the notifications the\nserver received.\n\nThe server simply exposes a blocking queue where notifications are placed when\nthey are received. It is your responsibility to timeout if a message is not\nreceived in a reasonable amount of time.\n\nFor example, in RSpec:\n\n```ruby\nrequire 'timeout'\n\ndescribe \"apple push notifications\" do\n  before do\n    @server = Grocer.server(port: 2195)\n    @server.accept # starts listening in background\n  end\n\n  after do\n    @server.close\n  end\n\n  specify \"As a user, I receive notifications on my phone when awesome things happen\" do\n    # ... exercise code that would send APNS notifications ...\n\n    Timeout.timeout(3) {\n      notification = @server.notifications.pop # blocking\n      expect(notification.alert).to eq(\"An awesome thing happened\")\n    }\n  end\nend\n```\n\n## Device Token\n\nA device token is obtained from within the iOS app. More details are in Apple's\n[Registering for Remote\nNotifications](http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/IPhoneOSClientImp/IPhoneOSClientImp.html#//apple_ref/doc/uid/TP40008194-CH103-SW1)\ndocumentation.\n\nThe key code for this purpose is:\n\n```objective-c\n- (void)applicationDidFinishLaunching:(UIApplication *)app {\n   // other setup tasks here....\n   [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];\n}\n\n- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {\n    NSLog(@\"Got device token: %@\", [devToken description]);\n\n    [self sendProviderDeviceToken:[devToken bytes]]; // custom method; e.g., send to a web service and store\n}\n\n- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {\n    NSLog(@\"Error in registration. Error: %@\", err);\n}\n```\n\n## Certificate File\n\nLogin to the [iOS Provisioning Portal (App IDs)](https://developer.apple.com/ios/manage/bundles/index.action).\n\nConfigure the appropriate certificate for push notifications and download the\ncertificate:\n\n![Downloading the Push Notification Certificate](https://img.skitch.com/20120402-gtj3bkqi1kq92kgw2pbr5puk5d.png)\n\nOpen the file in Keychain Access, then expand the certificate to show both the\ncertificate *and* the private key. Command select so both are highlighted:\n\n![Selecting both the certificate and private key](https://img.skitch.com/20120402-e8deartr2uhimaiatgccttkggi.png)\n\nControl click and select to export the 2 items:\n\n![Exporting the certificate and private key](https://img.skitch.com/20120402-mbmgjrybyym846cy58a9kpyxp5.png)\n\nSave the items as a `.p12` file. Open a terminal window and run the following\ncommand:\n\n```bash\nopenssl pkcs12 -in exported_certificate.p12 -out certificate.pem -nodes -clcerts\n```\n\nThe `certificate.pem` file that is generated can be used with **grocer**.\n\n## Support Channels\n\n[GitHub Issues](https://github.com/grocer/grocer/issues) and [Pull\nRequests](https://github.com/grocer/grocer/pulls) are the primary venues for\ncommunicating issues and discussing possible features. Several of us also\nregularly hang out in the `#grocer` channel on Freenode; feel free to pop in\nand ask questions there as well. Thanks! :heart:\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgithub%2Fgrocer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgithub%2Fgrocer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgithub%2Fgrocer/lists"}