houston 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +18 -0
- data/LICENSE +19 -0
- data/README.md +68 -0
- data/Rakefile +11 -0
- data/apple_push_notification_production.pem +136 -0
- data/houston.gemspec +22 -0
- data/lib/houston.rb +147 -0
- metadata +80 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# Houston
|
2
|
+
**Apple Push Notifications. No Dirigible Required**
|
3
|
+
|
4
|
+
> Houston, We Have Liftoff!
|
5
|
+
|
6
|
+
Push Notifications don't have to be difficult. And now they aren't.
|
7
|
+
|
8
|
+
Houston is a simple gem for sending Apple Push Notifications. Pass your credentials, construct your message, and send it.
|
9
|
+
|
10
|
+
In a production application, you will probably want to schedule or queue notifications into a background job. Whether you're using [queue_classic](https://github.com/ryandotsmith/queue_classic), [resque](https://github.com/defunkt/resque), or rolling you own infrastructure, integrating Houston couldn't be simpler.
|
11
|
+
|
12
|
+
Another caveat is that Houston doesn't manage device tokens for you. Since infrastructures can vary dramatically for these kinds of things, being agnostic and not forcing any conventions here is more a feature than a bug. That said, a simple web service adapter, similar to [Rack::CoreData](https://github.com/mattt/rack-core-data) may be in the cards.
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# Environment variables are automatically read, or can be overridden by any specified options
|
18
|
+
APN = Houston::Client.new
|
19
|
+
APN.certificate = File.read("/path/to/apple_push_notification.pem")
|
20
|
+
|
21
|
+
# An example of the token sent back when a device registers for notifications
|
22
|
+
token = "<42e33061 9695b86b 59e5452b 061774c9 726eb8ec 22982c4b 558e34e2 784adee0>"
|
23
|
+
|
24
|
+
# Create a notification that alerts a message to the user, plays a sound, and sets the badge on the app
|
25
|
+
notification = Houston::Notification.new(device: token)
|
26
|
+
notification.alert = "Hello, World!"
|
27
|
+
|
28
|
+
# Notifications can also change the badge count, have a custom sound, or pass along arbitrary data.
|
29
|
+
notification.badge = 57
|
30
|
+
notification.sound = "sosumi.aiff"
|
31
|
+
notification.custom_data = {foo: "bar"}
|
32
|
+
|
33
|
+
# And... sent! That's all it takes.
|
34
|
+
APN.push(notification)
|
35
|
+
```
|
36
|
+
|
37
|
+
## Converting Your Certificate
|
38
|
+
|
39
|
+
> These instructions come from the [APN on Rails](https://github.com/PRX/apn_on_rails) project, which is another great option for sending push notifications.
|
40
|
+
|
41
|
+
Once you have the certificate from Apple for your application, export your key
|
42
|
+
and the apple certificate as p12 files. Here is a quick walkthrough on how to do this:
|
43
|
+
|
44
|
+
1. Click the disclosure arrow next to your certificate in Keychain Access and select the certificate and the key.
|
45
|
+
2. Right click and choose `Export 2 items…`.
|
46
|
+
3. Choose the p12 format from the drop down and name it `cert.p12`.
|
47
|
+
|
48
|
+
Now covert the p12 file to a pem file:
|
49
|
+
|
50
|
+
```
|
51
|
+
$ openssl pkcs12 -in cert.p12 -out apple_push_notification_production.pem -nodes -clcerts
|
52
|
+
```
|
53
|
+
|
54
|
+
If you are using a development certificate, then change the name to apple_push_notification_development.pem instead.
|
55
|
+
|
56
|
+
Store the contents of the certificate files on the app model for the app you want to send notifications to.
|
57
|
+
|
58
|
+
## Contact
|
59
|
+
|
60
|
+
Mattt Thompson
|
61
|
+
|
62
|
+
- http://github.com/mattt
|
63
|
+
- http://twitter.com/mattt
|
64
|
+
- m@mattt.me
|
65
|
+
|
66
|
+
## License
|
67
|
+
|
68
|
+
Houston is available under the MIT license. See the LICENSE file for more info.
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
gemspec = eval(File.read("houston.gemspec"))
|
5
|
+
|
6
|
+
task :build => "#{gemspec.full_name}.gem"
|
7
|
+
|
8
|
+
file "#{gemspec.full_name}.gem" => gemspec.files + ["houston.gemspec"] do
|
9
|
+
system "gem build houston.gemspec"
|
10
|
+
system "gem install houston-#{Houston::Verson}.gem"
|
11
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
Bag Attributes
|
2
|
+
friendlyName: Apple Development IOS Push Services: com.heroku.heroku
|
3
|
+
localKeyID: 60 1B C7 72 74 46 A1 D4 9D 74 2D DE D6 E1 48 3C 0C 4E 35 56
|
4
|
+
subject=/UID=com.heroku.heroku/CN=Apple Development IOS Push Services: com.heroku.heroku/C=US
|
5
|
+
issuer=/C=US/O=Apple Inc./OU=Apple Worldwide Developer Relations/CN=Apple Worldwide Developer Relations Certification Authority
|
6
|
+
-----BEGIN CERTIFICATE-----
|
7
|
+
MIIFcTCCBFmgAwIBAgIIR6byv0WLtiowDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNV
|
8
|
+
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
|
9
|
+
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
|
10
|
+
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
|
11
|
+
HhcNMTIwOTA3MTY1NjMwWhcNMTMwOTA3MTY1NjMwWjBxMSEwHwYKCZImiZPyLGQB
|
12
|
+
AQwRY29tLmhlcm9rdS5oZXJva3UxPzA9BgNVBAMMNkFwcGxlIERldmVsb3BtZW50
|
13
|
+
IElPUyBQdXNoIFNlcnZpY2VzOiBjb20uaGVyb2t1Lmhlcm9rdTELMAkGA1UEBhMC
|
14
|
+
VVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7YxpLzpwjbY7RaSyB
|
15
|
+
oPOqqDxUJxkF9MUHOu6vBkVeaAzL1OEiwf43E+EieWDRnxX8tsBkJVlmtG+dteer
|
16
|
+
8nIfOKL4eoth9aleLwCIOyfgus6DTQI30MsR+HwyKF2ZD9ububKdRdoBXrLzVCUE
|
17
|
+
FHgd1a7qZZeQlExfrffV6cQItFd6V+1I40YvMPzktz42L4bvomjscRlpehxZh5aU
|
18
|
+
Le9Y2GLcwOslNqswRt1yRezDlcTqpopMg4JTzyUkTQ0YhxO0y7WBoSd4vNuCeSQp
|
19
|
+
9opTA7Jrsbnj3Ii93PnBgHa5QdJUSs8BNHuQnUJmISCws/Ka575b7/Ig5qbLE2AN
|
20
|
+
Vg/HAgMBAAGjggHlMIIB4TAdBgNVHQ4EFgQUYBvHcnRGodSddC3e1uFIPAxONVYw
|
21
|
+
CQYDVR0TBAIwADAfBgNVHSMEGDAWgBSIJxcJqbYYYIvs67r2R1nFUlSjtzCCAQ8G
|
22
|
+
A1UdIASCAQYwggECMIH/BgkqhkiG92NkBQEwgfEwgcMGCCsGAQUFBwICMIG2DIGz
|
23
|
+
UmVsaWFuY2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1l
|
24
|
+
cyBhY2NlcHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVy
|
25
|
+
bXMgYW5kIGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5k
|
26
|
+
IGNlcnRpZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4wKQYIKwYBBQUHAgEW
|
27
|
+
HWh0dHA6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvME0GA1UdHwRGMEQwQqBAoD6G
|
28
|
+
PGh0dHA6Ly9kZXZlbG9wZXIuYXBwbGUuY29tL2NlcnRpZmljYXRpb25hdXRob3Jp
|
29
|
+
dHkvd3dkcmNhLmNybDALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwIw
|
30
|
+
EAYKKoZIhvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAEbviaLfJahlvzGr
|
31
|
+
GzTyHf2HXDK+nqcszwbKBQm0Yf6lnITmHFcbK0QCVfrkXiEQUqg/3hCf3HcX2rQQ
|
32
|
+
/Wa8mnyHv42Lb1p1L7pJ1iWr5dB06pw7EeZxAt14dmhUhrspUq3pbxmVxc1wxBda
|
33
|
+
G5EcS16oPoqos2xU/hnCd5Te8zH2ks3wFxlzOWvSFQPFloU7HSHgtHGoAch3pfyb
|
34
|
+
f3OoO94/AQIUk2Vkv15856RrPuJSrz+krQm3zUBNhn6gMiNwR1drtKY7UvRBgia1
|
35
|
+
fbBwY4J0pXRl/FIEcIH46KCP5cp9gagMo7jFPnNOArPxPc1hi1GsmUG97hRH1KA7
|
36
|
+
QmGB4kM=
|
37
|
+
-----END CERTIFICATE-----
|
38
|
+
Bag Attributes
|
39
|
+
friendlyName: Apple Development IOS Push Services: com.heroku.heroku
|
40
|
+
localKeyID: 60 1B C7 72 74 46 A1 D4 9D 74 2D DE D6 E1 48 3C 0C 4E 35 56
|
41
|
+
subject=/UID=com.heroku.heroku/CN=Apple Development IOS Push Services: com.heroku.heroku/C=US
|
42
|
+
issuer=/C=US/O=Apple Inc./OU=Apple Worldwide Developer Relations/CN=Apple Worldwide Developer Relations Certification Authority
|
43
|
+
-----BEGIN CERTIFICATE-----
|
44
|
+
MIIFcTCCBFmgAwIBAgIIR6byv0WLtiowDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNV
|
45
|
+
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
|
46
|
+
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
|
47
|
+
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
|
48
|
+
HhcNMTIwOTA3MTY1NjMwWhcNMTMwOTA3MTY1NjMwWjBxMSEwHwYKCZImiZPyLGQB
|
49
|
+
AQwRY29tLmhlcm9rdS5oZXJva3UxPzA9BgNVBAMMNkFwcGxlIERldmVsb3BtZW50
|
50
|
+
IElPUyBQdXNoIFNlcnZpY2VzOiBjb20uaGVyb2t1Lmhlcm9rdTELMAkGA1UEBhMC
|
51
|
+
VVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7YxpLzpwjbY7RaSyB
|
52
|
+
oPOqqDxUJxkF9MUHOu6vBkVeaAzL1OEiwf43E+EieWDRnxX8tsBkJVlmtG+dteer
|
53
|
+
8nIfOKL4eoth9aleLwCIOyfgus6DTQI30MsR+HwyKF2ZD9ububKdRdoBXrLzVCUE
|
54
|
+
FHgd1a7qZZeQlExfrffV6cQItFd6V+1I40YvMPzktz42L4bvomjscRlpehxZh5aU
|
55
|
+
Le9Y2GLcwOslNqswRt1yRezDlcTqpopMg4JTzyUkTQ0YhxO0y7WBoSd4vNuCeSQp
|
56
|
+
9opTA7Jrsbnj3Ii93PnBgHa5QdJUSs8BNHuQnUJmISCws/Ka575b7/Ig5qbLE2AN
|
57
|
+
Vg/HAgMBAAGjggHlMIIB4TAdBgNVHQ4EFgQUYBvHcnRGodSddC3e1uFIPAxONVYw
|
58
|
+
CQYDVR0TBAIwADAfBgNVHSMEGDAWgBSIJxcJqbYYYIvs67r2R1nFUlSjtzCCAQ8G
|
59
|
+
A1UdIASCAQYwggECMIH/BgkqhkiG92NkBQEwgfEwgcMGCCsGAQUFBwICMIG2DIGz
|
60
|
+
UmVsaWFuY2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1l
|
61
|
+
cyBhY2NlcHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVy
|
62
|
+
bXMgYW5kIGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5k
|
63
|
+
IGNlcnRpZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4wKQYIKwYBBQUHAgEW
|
64
|
+
HWh0dHA6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvME0GA1UdHwRGMEQwQqBAoD6G
|
65
|
+
PGh0dHA6Ly9kZXZlbG9wZXIuYXBwbGUuY29tL2NlcnRpZmljYXRpb25hdXRob3Jp
|
66
|
+
dHkvd3dkcmNhLmNybDALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwIw
|
67
|
+
EAYKKoZIhvdjZAYDAQQCBQAwDQYJKoZIhvcNAQEFBQADggEBAEbviaLfJahlvzGr
|
68
|
+
GzTyHf2HXDK+nqcszwbKBQm0Yf6lnITmHFcbK0QCVfrkXiEQUqg/3hCf3HcX2rQQ
|
69
|
+
/Wa8mnyHv42Lb1p1L7pJ1iWr5dB06pw7EeZxAt14dmhUhrspUq3pbxmVxc1wxBda
|
70
|
+
G5EcS16oPoqos2xU/hnCd5Te8zH2ks3wFxlzOWvSFQPFloU7HSHgtHGoAch3pfyb
|
71
|
+
f3OoO94/AQIUk2Vkv15856RrPuJSrz+krQm3zUBNhn6gMiNwR1drtKY7UvRBgia1
|
72
|
+
fbBwY4J0pXRl/FIEcIH46KCP5cp9gagMo7jFPnNOArPxPc1hi1GsmUG97hRH1KA7
|
73
|
+
QmGB4kM=
|
74
|
+
-----END CERTIFICATE-----
|
75
|
+
Bag Attributes
|
76
|
+
friendlyName: Mattt Thompson
|
77
|
+
localKeyID: 60 1B C7 72 74 46 A1 D4 9D 74 2D DE D6 E1 48 3C 0C 4E 35 56
|
78
|
+
Key Attributes: <No Attributes>
|
79
|
+
-----BEGIN RSA PRIVATE KEY-----
|
80
|
+
MIIEogIBAAKCAQEAu2MaS86cI22O0WksgaDzqqg8VCcZBfTFBzrurwZFXmgMy9Th
|
81
|
+
IsH+NxPhInlg0Z8V/LbAZCVZZrRvnbXnq/JyHzii+HqLYfWpXi8AiDsn4LrOg00C
|
82
|
+
N9DLEfh8MihdmQ/bm7mynUXaAV6y81QlBBR4HdWu6mWXkJRMX6331enECLRXelft
|
83
|
+
SONGLzD85Lc+Ni+G76Jo7HEZaXocWYeWlC3vWNhi3MDrJTarMEbdckXsw5XE6qaK
|
84
|
+
TIOCU88lJE0NGIcTtMu1gaEneLzbgnkkKfaKUwOya7G549yIvdz5wYB2uUHSVErP
|
85
|
+
ATR7kJ1CZiEgsLPymue+W+/yIOamyxNgDVYPxwIDAQABAoIBAG0ODxbBRrODplOP
|
86
|
+
bxx+DHFsebfMDU8fFa/Qh2qLLsHzskbKPMBawFVTl3nVxBgbCtPJlAx0hCQ2rKLP
|
87
|
+
Y+X5Mpui/Y242/eF3iz3E8s1Ck9KyLc3kSvkstphj/4s6dQTm450s1O+fFLBx0m/
|
88
|
+
1c8bDpDmdGbr+xpkhFJGz+7JnpIlSqyg3f3MSVa85WdyWwxY2nVOkZqyisUO/Ec2
|
89
|
+
4UjRe1qx6g6SqKGSlWRDDYb74akCua8DtPVNpmRVHr/hOjo3nTOjEcozxt4zsu+D
|
90
|
+
S6HxaDPcfGbffgPNZZag/jK/a2RYvOhixC0vgi/ApiYgrOUW9Zy5Z5wxA/XvWkto
|
91
|
+
mM96HlECgYEA78W62cbbKB6//O5VrhAR436pA4z7JMJa99C/MuB5Ntq2uKS09djC
|
92
|
+
KMfAQ8uS98naAfF6r6WTKjZrlCKtFrxGAWZ1ky//ODgTV0BKUFXjwJz+mwyl09VX
|
93
|
+
Ltb46TZkBrZPkMg7Tg+zK9iY4XO2W0UGyxgrnHequlnSyOaY02vkUbkCgYEAyBHA
|
94
|
+
YjAA1YV9QvjcAyjxMXwmPulllQYY4oqMBNnU0OVoAfewFy3QTVaKoPeI7d5JJo9h
|
95
|
+
wEtUisNg39hCY/hT8wpE1aPPSWMoYMceNS0/d+QE1aAc2pBcThhvOyjDfXGUC6to
|
96
|
+
Uah5gndNPMjWejwtTq8FJ/Ec4oQoUQYiEjRULX8CgYBokN3k/bEz9P0bbEFXcNCs
|
97
|
+
Wt+Qmdoo9M3BR3sqz/Rs9MM9ES+OC+LY8AwsRMqqX5BAyrQLh/T/Ms6Fwv2jbaZG
|
98
|
+
FA3wi70NIn9kzLfcAykZJh5vNfvXvzGPTtcstbi3qKhYZspeR8CGvaYmpFUBxr+o
|
99
|
+
sLjMOu/s5yFi1hYOFf5zmQKBgCOZLP8LhYp4hxI8j4EsbsYcTf3Rr5Z1kEX22vUe
|
100
|
+
AUTuHLSE6tsk9rRcGqyLfty8OY3X40/A15kdLIaHx0Bcdit/f0H4NLDdKNkXdOOH
|
101
|
+
WoytkMKmC5xEJXhr1pbTKP2QEQSGijhFXuF88sxG/Aqh3HnsoGE8AKS5WScZjURp
|
102
|
+
9TRnAoGAUfUJA15pVeT/wLbamPu1g0E8F/EdpI5GKJJHiHfCz++A1Spj7hMP2mSc
|
103
|
+
y+izt5A5PUsrD3hlYDd9z6+2O3rIVApcRytIegbhaXPSnYQMAiF+BN/Kd/D8tOa7
|
104
|
+
FrF/ldCMG+Kj1CnwLZkqLPPZmJYKa7HGuhYFfmQG1k9jIUdNsMw=
|
105
|
+
-----END RSA PRIVATE KEY-----
|
106
|
+
Bag Attributes
|
107
|
+
friendlyName: Mattt Thompson
|
108
|
+
localKeyID: 60 1B C7 72 74 46 A1 D4 9D 74 2D DE D6 E1 48 3C 0C 4E 35 56
|
109
|
+
Key Attributes: <No Attributes>
|
110
|
+
-----BEGIN RSA PRIVATE KEY-----
|
111
|
+
MIIEogIBAAKCAQEAu2MaS86cI22O0WksgaDzqqg8VCcZBfTFBzrurwZFXmgMy9Th
|
112
|
+
IsH+NxPhInlg0Z8V/LbAZCVZZrRvnbXnq/JyHzii+HqLYfWpXi8AiDsn4LrOg00C
|
113
|
+
N9DLEfh8MihdmQ/bm7mynUXaAV6y81QlBBR4HdWu6mWXkJRMX6331enECLRXelft
|
114
|
+
SONGLzD85Lc+Ni+G76Jo7HEZaXocWYeWlC3vWNhi3MDrJTarMEbdckXsw5XE6qaK
|
115
|
+
TIOCU88lJE0NGIcTtMu1gaEneLzbgnkkKfaKUwOya7G549yIvdz5wYB2uUHSVErP
|
116
|
+
ATR7kJ1CZiEgsLPymue+W+/yIOamyxNgDVYPxwIDAQABAoIBAG0ODxbBRrODplOP
|
117
|
+
bxx+DHFsebfMDU8fFa/Qh2qLLsHzskbKPMBawFVTl3nVxBgbCtPJlAx0hCQ2rKLP
|
118
|
+
Y+X5Mpui/Y242/eF3iz3E8s1Ck9KyLc3kSvkstphj/4s6dQTm450s1O+fFLBx0m/
|
119
|
+
1c8bDpDmdGbr+xpkhFJGz+7JnpIlSqyg3f3MSVa85WdyWwxY2nVOkZqyisUO/Ec2
|
120
|
+
4UjRe1qx6g6SqKGSlWRDDYb74akCua8DtPVNpmRVHr/hOjo3nTOjEcozxt4zsu+D
|
121
|
+
S6HxaDPcfGbffgPNZZag/jK/a2RYvOhixC0vgi/ApiYgrOUW9Zy5Z5wxA/XvWkto
|
122
|
+
mM96HlECgYEA78W62cbbKB6//O5VrhAR436pA4z7JMJa99C/MuB5Ntq2uKS09djC
|
123
|
+
KMfAQ8uS98naAfF6r6WTKjZrlCKtFrxGAWZ1ky//ODgTV0BKUFXjwJz+mwyl09VX
|
124
|
+
Ltb46TZkBrZPkMg7Tg+zK9iY4XO2W0UGyxgrnHequlnSyOaY02vkUbkCgYEAyBHA
|
125
|
+
YjAA1YV9QvjcAyjxMXwmPulllQYY4oqMBNnU0OVoAfewFy3QTVaKoPeI7d5JJo9h
|
126
|
+
wEtUisNg39hCY/hT8wpE1aPPSWMoYMceNS0/d+QE1aAc2pBcThhvOyjDfXGUC6to
|
127
|
+
Uah5gndNPMjWejwtTq8FJ/Ec4oQoUQYiEjRULX8CgYBokN3k/bEz9P0bbEFXcNCs
|
128
|
+
Wt+Qmdoo9M3BR3sqz/Rs9MM9ES+OC+LY8AwsRMqqX5BAyrQLh/T/Ms6Fwv2jbaZG
|
129
|
+
FA3wi70NIn9kzLfcAykZJh5vNfvXvzGPTtcstbi3qKhYZspeR8CGvaYmpFUBxr+o
|
130
|
+
sLjMOu/s5yFi1hYOFf5zmQKBgCOZLP8LhYp4hxI8j4EsbsYcTf3Rr5Z1kEX22vUe
|
131
|
+
AUTuHLSE6tsk9rRcGqyLfty8OY3X40/A15kdLIaHx0Bcdit/f0H4NLDdKNkXdOOH
|
132
|
+
WoytkMKmC5xEJXhr1pbTKP2QEQSGijhFXuF88sxG/Aqh3HnsoGE8AKS5WScZjURp
|
133
|
+
9TRnAoGAUfUJA15pVeT/wLbamPu1g0E8F/EdpI5GKJJHiHfCz++A1Spj7hMP2mSc
|
134
|
+
y+izt5A5PUsrD3hlYDd9z6+2O3rIVApcRytIegbhaXPSnYQMAiF+BN/Kd/D8tOa7
|
135
|
+
FrF/ldCMG+Kj1CnwLZkqLPPZmJYKa7HGuhYFfmQG1k9jIUdNsMw=
|
136
|
+
-----END RSA PRIVATE KEY-----
|
data/houston.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "houston"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "houston"
|
7
|
+
s.authors = ["Mattt Thompson"]
|
8
|
+
s.email = "m@mattt.me"
|
9
|
+
s.homepage = "http://github.com/mattt/houston"
|
10
|
+
s.version = Houston::Version
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.summary = "iOS Push Notifications"
|
13
|
+
s.description = ""
|
14
|
+
|
15
|
+
s.add_development_dependency "rspec", "~> 0.6.1"
|
16
|
+
s.add_development_dependency "rake", "~> 0.9.2"
|
17
|
+
|
18
|
+
s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
end
|
data/lib/houston.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'socket'
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Houston
|
7
|
+
Version = "0.0.1"
|
8
|
+
|
9
|
+
APPLE_PRODUCTION_GATEWAY_URI = "apn://gateway.push.apple.com:2195"
|
10
|
+
APPLE_PRODUCTION_FEEDBACK_URI = "apn://feedback.push.apple.com:2196"
|
11
|
+
|
12
|
+
APPLE_DEVELOPMENT_GATEWAY_URI = "apn://gateway.sandbox.push.apple.com:2195"
|
13
|
+
APPLE_DEVELOPMENT_FEEDBACK_URI = "apn://feedback.push.apple.com:2196"
|
14
|
+
|
15
|
+
class Client
|
16
|
+
attr_accessor :gateway_uri, :feedback_uri, :certificate, :passphrase
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@gateway_uri = ENV['APN_GATEWAY_URI']
|
20
|
+
@feedback_uri = ENV['APN_FEEDBACK_URI']
|
21
|
+
@certificate = ENV['APN_CERTIFICATE']
|
22
|
+
@passphrase = ENV['APN_CERTIFICATE_PASSPHRASE']
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.development
|
26
|
+
client = self.new
|
27
|
+
client.gateway_uri = APPLE_DEVELOPMENT_GATEWAY_URI
|
28
|
+
client.feedback_uri = APPLE_DEVELOPMENT_FEEDBACK_URI
|
29
|
+
client
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.production
|
33
|
+
client = self.new
|
34
|
+
client.gateway_uri = APPLE_PRODUCTION_GATEWAY_URI
|
35
|
+
client.feedback_uri = APPLE_PRODUCTION_FEEDBACK_URI
|
36
|
+
client
|
37
|
+
end
|
38
|
+
|
39
|
+
def push(*notifications)
|
40
|
+
Connection.open(connection_options_for_endpoint(:gateway)) do |connection, socket|
|
41
|
+
notifications.each do |notification|
|
42
|
+
next if notification.sent?
|
43
|
+
|
44
|
+
connection.write(notification.message)
|
45
|
+
notification.mark_as_sent!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def devices
|
51
|
+
devices = []
|
52
|
+
|
53
|
+
Connection.open(connection_options_for_endpoint(:feedback)) do |connection, socket|
|
54
|
+
while line = connection.read(38)
|
55
|
+
feedback = line.unpack('N1n1H140')
|
56
|
+
token = feedback[2].scan(/.{0,8}/).join(' ').strip
|
57
|
+
devices << token if token
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
devices
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def connection_options_for_endpoint(endpoint = :gateway)
|
67
|
+
uri = case endpoint
|
68
|
+
when :gateway then URI(@gateway_uri)
|
69
|
+
when :feedback then URI(@feedback_uri)
|
70
|
+
else
|
71
|
+
raise ArgumentError
|
72
|
+
end
|
73
|
+
|
74
|
+
{
|
75
|
+
certificate: @certificate,
|
76
|
+
passphrase: @passphrase,
|
77
|
+
host: uri.host,
|
78
|
+
port: uri.port
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Notification
|
84
|
+
attr_accessor :device, :alert, :badge, :sound, :custom_data
|
85
|
+
attr_reader :sent_at
|
86
|
+
|
87
|
+
def initialize(options = {})
|
88
|
+
@device = options.delete(:device)
|
89
|
+
@alert = options.delete(:alert)
|
90
|
+
@badge = options.delete(:badge)
|
91
|
+
@sound = options.delete(:sound)
|
92
|
+
|
93
|
+
@custom_data = options
|
94
|
+
end
|
95
|
+
|
96
|
+
def payload
|
97
|
+
json = {}.merge(@custom_data || {})
|
98
|
+
json['aps'] = {}
|
99
|
+
json['aps']['alert'] = @alert
|
100
|
+
json['aps']['badge'] = @badge.to_i rescue 0
|
101
|
+
json['aps']['sound'] = @sound
|
102
|
+
|
103
|
+
json
|
104
|
+
end
|
105
|
+
|
106
|
+
def message
|
107
|
+
json = payload.to_json
|
108
|
+
|
109
|
+
"\0\0 #{[@device.gsub(/[<\s>]/, '')].pack('H*')}\0#{json.length.chr}#{json}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def mark_as_sent!
|
113
|
+
@sent_at = Time.now
|
114
|
+
end
|
115
|
+
|
116
|
+
def sent?
|
117
|
+
!!@sent_at
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Connection
|
122
|
+
class << self
|
123
|
+
def open(options = {})
|
124
|
+
return unless block_given?
|
125
|
+
|
126
|
+
[:certificate, :passphrase, :host, :port].each do |option|
|
127
|
+
raise ArgumentError, "Missing connection parameter: #{option}" unless option
|
128
|
+
end
|
129
|
+
|
130
|
+
socket = TCPSocket.new(options[:host], options[:port])
|
131
|
+
|
132
|
+
context = OpenSSL::SSL::SSLContext.new
|
133
|
+
context.key = OpenSSL::PKey::RSA.new(options[:certificate], options[:passphrase])
|
134
|
+
context.cert = OpenSSL::X509::Certificate.new(options[:certificate])
|
135
|
+
|
136
|
+
ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
|
137
|
+
ssl.sync = true
|
138
|
+
ssl.connect
|
139
|
+
|
140
|
+
yield ssl, socket
|
141
|
+
|
142
|
+
ssl.close
|
143
|
+
socket.close
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: houston
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mattt Thompson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-07 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70324475112900 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.6.1
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70324475112900
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70324475112400 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.9.2
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70324475112400
|
36
|
+
description: ''
|
37
|
+
email: m@mattt.me
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- ./apple_push_notification_production.pem
|
43
|
+
- ./Gemfile
|
44
|
+
- ./Gemfile.lock
|
45
|
+
- ./houston.gemspec
|
46
|
+
- ./lib/houston.rb
|
47
|
+
- ./LICENSE
|
48
|
+
- ./Rakefile
|
49
|
+
- ./README.md
|
50
|
+
homepage: http://github.com/mattt/houston
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
hash: 1752936197030512533
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
hash: 1752936197030512533
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.15
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: iOS Push Notifications
|
80
|
+
test_files: []
|