mameapns 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,27 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rspec
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+
20
+ vendor
21
+
22
+ # for emacs
23
+ *~
24
+ \#*
25
+ .\#*
26
+
27
+ UTF-8
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mameapns.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ notification :growl
5
+
6
+ guard 'rspec', notification: true do
7
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
+ watch(%r{^spec/(.+)\.rb$}) { |m| "spec/#{m[1]}.rb" }
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 sutetotanuki
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Mameapns
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'mameapns'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install mameapns
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ Bundler.setup
4
+
5
+ require "rspec"
6
+ require "rspec/core/rake_task"
7
+
8
+ RSpec::Core::RakeTask.new(:rspec) do |config|
9
+ end
10
+
11
+ task default: :spec
data/bin/mameapns ADDED
@@ -0,0 +1,130 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "bundler"
4
+ Bundler.setup
5
+ require "thor"
6
+
7
+ $:.unshift File.expand_path("../../lib", __FILE__)
8
+
9
+ require "mameapns"
10
+
11
+ module Mameapns
12
+ class MameDRb
13
+ def initialize(queue)
14
+ @queue = queue
15
+ end
16
+
17
+ def deliver(token, alert)
18
+ @queue.push([token, alert])
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ class Mameapns::Runner < Thor
25
+ desc "deliver", "send a notification to your device."
26
+ option :cert, type: :string, aliases: "-c", desc: "certificate file", required: true
27
+ option :pass, type: :string, aliases: "-p", desc: "certificate password", required: true
28
+ option :token, type: :string, aliases: "-t", desc: "target devise token", required: true
29
+ option :develop, type: :boolean, aliases: "-d", desc: "developer mode", default: false
30
+
31
+ def deliver
32
+ unless File.file?(options[:cert])
33
+ puts "#{options[:cert]} is invalid file or not exists."
34
+ exit(1)
35
+ end
36
+
37
+ cert = File.read(options[:cert])
38
+ pass = options[:pass]
39
+
40
+ @apns = Mameapns.new(
41
+ ssl_cert: cert,
42
+ ssl_cert_key: cert,
43
+ ssl_cert_pass: pass,
44
+ develop: options[:develop]
45
+ )
46
+
47
+ @apns.on_sent do |notification|
48
+ puts "Sent notification complete."
49
+ p notification
50
+ @apns.stop
51
+ end
52
+
53
+ @apns.on_delivery_error do |notification, error|
54
+ puts "error occured during sending notification."
55
+ p error
56
+ @apns.stop
57
+ end
58
+
59
+ @apns.on_exception do |error|
60
+ puts "critical error occured during sending notification."
61
+ p error
62
+ @apns.stop
63
+ end
64
+
65
+ @apns.start
66
+
67
+ @apns.deliver(
68
+ Mameapns::Notification.new(
69
+ device_token: options[:token],
70
+ alert: "test message"
71
+ ))
72
+
73
+ @apns.wait_stop
74
+ end
75
+
76
+ desc "drb", "Run drb server that provides handle apns notifications."
77
+ option :cert, type: :string, aliases: "-c", desc: "certificate file", required: true
78
+ option :pass, type: :string, aliases: "-p", desc: "certificate password", required: true
79
+ option :develop, type: :boolean, aliases: "-d", desc: "developer mode", default: false
80
+ option :port, type: :numeric, aliases: "-P", desc: "listen port", default: 11875
81
+ def drb
82
+ require "drb/drb"
83
+ require "thread"
84
+
85
+ unless File.file?(options[:cert])
86
+ puts "#{options[:cert]} is invalid file or not exists."
87
+ exit(1)
88
+ end
89
+
90
+ @queue = Queue.new
91
+
92
+ cert = File.read(options[:cert])
93
+ pass = options[:pass]
94
+
95
+ @apns = Mameapns.new(
96
+ ssl_cert: cert,
97
+ ssl_cert_key: cert,
98
+ ssl_cert_pass: pass,
99
+ develop: options[:develop]
100
+ )
101
+
102
+ @apns.start
103
+
104
+ drb_server = DRb::DRbServer.new("druby://localhost:#{options[:port]}", Mameapns::MameDRb.new(@queue))
105
+
106
+ [:TERM, :QUIT, :INT].each do |sig|
107
+ trap(sig) do
108
+ puts "Going to shutdown..."
109
+ drb_server.stop_service
110
+ @apns.stop
111
+ @apns.wait_stop
112
+ exit(1)
113
+ end
114
+ end
115
+
116
+
117
+ while tuple = @queue.pop
118
+ @apns.deliver(
119
+ Mameapns::Notification.new(
120
+ device_token: tuple[0],
121
+ alert: tuple[1]
122
+ ))
123
+
124
+ end
125
+
126
+ # drb_server.stop_server
127
+ end
128
+ end
129
+
130
+ Mameapns::Runner.start
data/lib/mameapns.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "thread"
2
+ require "openssl"
3
+ require "json"
4
+
5
+ module Mameapns
6
+ require "mameapns/error"
7
+ require "mameapns/version"
8
+
9
+ autoload :Options, 'mameapns/options'
10
+ autoload :Notification, 'mameapns/notification'
11
+ autoload :Application, 'mameapns/application'
12
+ autoload :Session, 'mameapns/session'
13
+ autoload :InterruptibleSleep, 'mameapns/interruptible_sleep'
14
+ autoload :Connection, 'mameapns/connection'
15
+
16
+ def self.new(options)
17
+ Application.new(options)
18
+ end
19
+ end
@@ -0,0 +1,103 @@
1
+ module Mameapns
2
+ class Application
3
+ include Options
4
+
5
+ options :host
6
+ options :port, default: 2195
7
+ options :feedback_host
8
+ options :feedback_port, default: 2196
9
+ options :ssl_cert
10
+ options :ssl_cert_key
11
+ options :ssl_cert_pass
12
+ options :develop, default: false
13
+
14
+ def host
15
+ @host ||= if develop
16
+ "gateway.sandbox.push.apple.com"
17
+ else
18
+ "gateway.push.apple.com"
19
+ end
20
+ end
21
+
22
+ def feedback_host
23
+ @feedback_host ||= if develop
24
+ "feedback.sandbox.push.apple.com"
25
+ else
26
+ "feedback.push.apple.com"
27
+ end
28
+ end
29
+
30
+ def start
31
+ @deliver_session = Session::Deliver.new(
32
+ ssl_cert: ssl_cert,
33
+ ssl_cert_key: ssl_cert_key,
34
+ ssl_cert_pass: ssl_cert_pass,
35
+ host: host,
36
+ port: port
37
+ )
38
+
39
+ @feedback_session = Session::Feedback.new(
40
+ ssl_cert: ssl_cert,
41
+ ssl_cert_key: ssl_cert_key,
42
+ ssl_cert_pass: ssl_cert_pass,
43
+ host: feedback_host,
44
+ port: feedback_port
45
+ )
46
+
47
+ @deliver_session.on_sent(&method(:handle_sent))
48
+ @deliver_session.on_error(&method(:handle_delivery_error))
49
+ @deliver_session.on_exception(&method(:handle_exception))
50
+
51
+ @feedback_session.on_error(&method(:handle_feedback))
52
+ @feedback_session.on_exception(&method(:handle_exception))
53
+
54
+ @deliver_session.start
55
+ @feedback_session.start
56
+ end
57
+
58
+ def stop
59
+ @deliver_session.stop
60
+ @feedback_session.stop
61
+ end
62
+
63
+ def wait_stop
64
+ @deliver_session.wait_stop
65
+ @feedback_session.wait_stop
66
+ end
67
+
68
+ def deliver(notification)
69
+ @deliver_session.push(notification)
70
+ end
71
+
72
+ def handle_delivery_error(notification, err)
73
+ @on_delivery_error.call(notification, err) if @on_delivery_error
74
+ end
75
+
76
+ def handle_feedback(notification, err)
77
+ @on_feedback.call(err.device_token) if @on_feedback
78
+ end
79
+
80
+ def handle_exception(e)
81
+ @on_exception.call(e) if @on_exception
82
+ end
83
+
84
+ def handle_sent(notification)
85
+ @on_sent.call(notification) if @on_sent
86
+ end
87
+
88
+ def on_delivery_error(&block)
89
+ @on_delivery_error = block
90
+ end
91
+
92
+ [:feedback, :sent, :delivery_error, :exception].each do |event|
93
+ event_name = "on_#{event}"
94
+ define_method(event_name) do |&block|
95
+ instance_variable_set("@#{event_name}", block)
96
+ end
97
+ end
98
+
99
+ def on_exception(&block)
100
+ @on_exception = block
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,110 @@
1
+ require 'openssl'
2
+ require 'socket'
3
+
4
+ module Mameapns
5
+
6
+ class Connection
7
+ attr_accessor :ssl_cert, :ssl_cert_key, :ssl_cert_pass, :host, :port, :last_write
8
+
9
+ IDLE_PERIOD = 1800
10
+
11
+ def initialize(host, port, options={})
12
+ self.host = host
13
+ self.port = port
14
+
15
+ options.each do |k, v|
16
+ setter_name = "#{k}="
17
+ self.__send__(setter_name, v) if respond_to?(setter_name)
18
+ end
19
+
20
+ written
21
+ end
22
+
23
+ def connect
24
+ setup_ssl_context
25
+ @tcp_socket, @ssl_socket = connect_socket
26
+ end
27
+
28
+ def close
29
+ begin
30
+ @tcp_socket.close
31
+ @ssl_socket.close
32
+ rescue IOError
33
+ end
34
+ end
35
+
36
+ def read(bytesize)
37
+ @ssl_socket.read(bytesize)
38
+ end
39
+
40
+ def select(timeout)
41
+ IO.select([@ssl_socket], nil, nil, timeout)
42
+ end
43
+
44
+ def reconnect
45
+ close
46
+ @tcp_socket, @ssl_socket = connect_socket
47
+ end
48
+
49
+ def write(data)
50
+ reconnect if idle_period_exceeded?
51
+
52
+ retry_count = 0
53
+
54
+ begin
55
+ write_data(data)
56
+ rescue Errno::EPIPE, Errno::ETIMEOUT, OpenSSL::SSL::SSLError => e
57
+ retry_count += 1
58
+
59
+ if retry_count <= 3
60
+ reconnect
61
+ sleep 1
62
+ retry
63
+ else
64
+ raise ConnectionError, "Attempt to reconnect but failed"
65
+ end
66
+ end
67
+ end
68
+
69
+ def write_data(data)
70
+ @ssl_socket.write(data)
71
+ @ssl_socket.flush
72
+ written
73
+ end
74
+
75
+ def idle_period_exceeded?
76
+ Time.now - last_write > IDLE_PERIOD
77
+ end
78
+
79
+ def written
80
+ self.last_write = Time.now
81
+ end
82
+
83
+ def setup_ssl_context
84
+ begin
85
+ @ssl_context = OpenSSL::SSL::SSLContext.new
86
+ @ssl_context.key = OpenSSL::PKey::RSA.new(ssl_cert_key, ssl_cert_pass)
87
+ @ssl_context.cert = OpenSSL::X509::Certificate.new(ssl_cert)
88
+ @ssl_context
89
+ rescue OpenSSL::PKey::RSAError => e
90
+ raise SSLError, "Neither PUB key nor PRIV key:: nested asn1 error"
91
+ end
92
+ end
93
+
94
+ def connect_socket
95
+ begin
96
+ tcp_socket = TCPSocket.new(host, port)
97
+ tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
98
+ tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
99
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
100
+ ssl_socket.sync = true
101
+ ssl_socket.connect
102
+ [tcp_socket, ssl_socket]
103
+ rescue OpenSSL::SSL::SSLError => e
104
+ raise SSLError, e
105
+ rescue SocketError => e
106
+ raise OpenConnectionError, e
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,20 @@
1
+ module Mameapns
2
+ class Error < StandardError; end
3
+
4
+ # Connection error
5
+ # never succeed to send notificaiont while
6
+ # resolve under problems.
7
+ class ConnectionError < Error; end
8
+ class SSLError < ConnectionError; end
9
+
10
+ # Delivery error
11
+ class DeliveryError < Error
12
+ attr_accessor :code, :description
13
+
14
+ def initialize(code, description)
15
+ @code, @description = code, description
16
+ super(description)
17
+ end
18
+ end
19
+ class DisconnectionError < Error; end
20
+ end
@@ -0,0 +1,16 @@
1
+ module Mameapns
2
+ class InterruptibleSleep
3
+ def wait(seconds)
4
+ @l, @r = IO.pipe
5
+ IO.select([@l], nil, nil, @seconds)
6
+ @l.close rescue IOError
7
+ @r.close rescue IOError
8
+ end
9
+
10
+ def interrupt
11
+ if @r
12
+ @r.close rescue IOError
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ module Mameapns
2
+ class Notification
3
+ include Options
4
+
5
+ options :axion_id
6
+ options :medium_id
7
+
8
+ options :identifier, default: 0
9
+ options :expiry, default: 86400
10
+ options :device_token
11
+ options :badge
12
+ options :sound, default: "1.aiff"
13
+ options :alert
14
+ options :attributes_for_device
15
+ options :related_infomation
16
+
17
+ def device_token=(token)
18
+ if !token.nil?
19
+ @device_token = token.gsub(/[ <>]/, "")
20
+ else
21
+ @device_token = nil
22
+ end
23
+ end
24
+
25
+ def as_json
26
+ json = {}
27
+ json['aps'] = {}
28
+ json['aps']['alert'] = alert if alert
29
+ json['aps']['badge'] = badge if badge
30
+ json['aps']['sound'] = sound if sound
31
+ json['aps'].merge!(attributes_for_device) if attributes_for_device
32
+ json
33
+ end
34
+
35
+ def payload
36
+ as_json.to_json
37
+ end
38
+
39
+ def payload_size
40
+ payload.bytesize
41
+ end
42
+
43
+ def to_binary
44
+ [1, identifier, expiry, 0, 32, device_token, 0, payload_size, payload].pack("cNNccH*cca*")
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,56 @@
1
+ module Mameapns
2
+ module Options
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ def initialize(values={})
8
+ # like a sybolize_keys in active support
9
+ values.keys.each { |k| values[k.to_sym] = values.delete(k) }
10
+
11
+ self.class.attrs.each do |attr_name, option|
12
+ if values.keys.include?(attr_name)
13
+ self.__send__("#{attr_name}=", values[attr_name])
14
+ else
15
+ if option[:default]
16
+ self.__send__("#{attr_name}=", option[:default])
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def to_hash
23
+ hash = {}
24
+ self.class.attrs.each do |attr_name, option|
25
+ value = self.send(attr_name)
26
+ if value.respond_to?(:to_hash)
27
+ hash[attr_name.to_s] = value.to_hash
28
+ else
29
+ hash[attr_name.to_s] = value
30
+ end
31
+ end
32
+
33
+ hash
34
+ end
35
+
36
+ def to_json
37
+ to_hash.to_json
38
+ end
39
+
40
+ alias :initialize_options :initialize
41
+
42
+ module ClassMethods
43
+ def attrs
44
+ @attrs ||= {}
45
+ end
46
+
47
+ def options(name, option={})
48
+ class_eval do
49
+ attr_accessor name
50
+ end
51
+
52
+ attrs[name] = option
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,115 @@
1
+ module Mameapns
2
+ class Session
3
+ attr_accessor :connection, :ssl_cert, :ssl_cert_key, :ssl_cert_pass, :host, :port, :queue, :interruptible_sleep
4
+
5
+ Dir["#{File.expand_path("../", __FILE__)}/session/*.rb"].each do |file|
6
+ require file
7
+ end
8
+
9
+ def initialize(options)
10
+ options.each do |k, v|
11
+ setter_name = "#{k}="
12
+ self.__send__(setter_name, v) if respond_to?(setter_name)
13
+ end
14
+
15
+ @connection ||= Connection.new(host, port,
16
+ ssl_cert: ssl_cert,
17
+ ssl_cert_key: ssl_cert_key,
18
+ ssl_cert_pass: ssl_cert_pass,
19
+ )
20
+
21
+ @on_exception = nil
22
+ @on_error = nil
23
+
24
+ @thread = nil
25
+ @queue = Queue.new
26
+ @running = false
27
+ @interruptible_sleep = InterruptibleSleep.new
28
+ end
29
+
30
+ def start
31
+ # connect to apns.
32
+ @running = true
33
+
34
+ @thread = Thread.start do
35
+ begin
36
+ start_session
37
+ rescue => e
38
+ @on_exception.call(e) if @on_exception
39
+ retry
40
+ end
41
+ end
42
+ end
43
+
44
+ def start_session
45
+ # expect to implement by sub class
46
+ end
47
+
48
+ def end_session
49
+ # expect to implement by sub class
50
+ end
51
+
52
+ def handle_error(notification, err)
53
+ @on_error.call(notification, err) if @on_error
54
+ end
55
+
56
+ def handle_sent(notification)
57
+ @on_sent.call(notification) if @on_sent
58
+ end
59
+
60
+ def stop
61
+ @queue << nil
62
+ @running = false
63
+ @interruptible_sleep.interrupt
64
+ sleep 1 # FIXME: magic number
65
+ end
66
+
67
+ def wait_stop
68
+ @thread.join
69
+ end
70
+
71
+ def running?
72
+ !!@running
73
+ end
74
+
75
+ def push(data)
76
+ @queue << data
77
+ end
78
+
79
+ def select(wait_time)
80
+ @connection.select(wait_time)
81
+ end
82
+
83
+ def read(size)
84
+ @connection.read(size)
85
+ end
86
+
87
+ def write(data)
88
+ @connection.write(data)
89
+ end
90
+
91
+ def connect
92
+ @connection.connect
93
+ end
94
+
95
+ def close
96
+ @connection.close
97
+ end
98
+
99
+ def reconnect
100
+ @connection.reconnect
101
+ end
102
+
103
+ def on_exception(&block)
104
+ @on_exception = block
105
+ end
106
+
107
+ def on_error(&block)
108
+ @on_error = block
109
+ end
110
+
111
+ def on_sent(&block)
112
+ @on_sent = block
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,57 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Mameapns
3
+ class Session
4
+ class Deliver < Session
5
+ SELECT_TIMEOUT = 0.5
6
+ ERROR_TUPLE_BYTES = 6
7
+
8
+ APN_ERRORS = {
9
+ 1 => "Processing error",
10
+ 2 => "Missing device token",
11
+ 3 => "Missing topic",
12
+ 4 => "Missing payload",
13
+ 5 => "Missing token size",
14
+ 6 => "Missing topic size",
15
+ 7 => "Missing payload size",
16
+ 8 => "Invalid token",
17
+ 255 => "None (unknown error)"
18
+ }
19
+
20
+ def start_session
21
+ connect
22
+
23
+ while notification = queue.pop
24
+ deliver(notification)
25
+ end
26
+
27
+ close
28
+ end
29
+
30
+ def deliver(notification)
31
+ write(notification.to_binary)
32
+
33
+ check_for_error(notification)
34
+
35
+ handle_sent(notification)
36
+ end
37
+
38
+ def check_for_error(notification)
39
+ if select(SELECT_TIMEOUT)
40
+ if tuple = read(ERROR_TUPLE_BYTES)
41
+ cmd, code, notification_id = tuple.unpack("ccN")
42
+
43
+ description = APN_ERRORS[code.to_i] || "Unknown error."
44
+ handle_error(notification, Mameapns::DeliveryError.new(code, description))
45
+ else
46
+ handle_error(notification, Mameapns::DisconnectionError.new)
47
+ end
48
+
49
+ begin
50
+ # エラーをうけとったらreconnectする。
51
+ reconnect
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,24 @@
1
+ module Mameapns
2
+ class Session
3
+ class Feedback < Session
4
+
5
+ TAPLE_BYTES = 38
6
+ POLL = 60
7
+
8
+ def start_session
9
+ while running?
10
+ connect
11
+
12
+ while tuple = read(TAPLE_BYTES)
13
+ timestamp, _, device_token = tuple.unpack('N1n1H*')
14
+ handler_error(nil, DeviceNotExist.new(timestamp, device_token))
15
+ end
16
+
17
+ close
18
+
19
+ interruptible_sleep.wait(POLL)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module Mameapns
2
+ VERSION = "0.0.1"
3
+ end
data/mameapns.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mameapns/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "mameapns"
8
+ gem.version = Mameapns::VERSION
9
+ gem.authors = ["sutetotanuki"]
10
+ gem.email = ["sutetotanuki@gmail.com"]
11
+ gem.description = "mame apns"
12
+ gem.summary = "mame apns"
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.add_runtime_dependency("thor")
20
+ gem.add_development_dependency("rspec")
21
+ gem.add_development_dependency("guard-rspec")
22
+ gem.add_development_dependency("growl")
23
+ gem.add_development_dependency("rb-fsevent", "~> 0.9.1")
24
+ end
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+ module Mameapns
4
+ describe Notification do
5
+ it "should strip chevrons from the given string" do
6
+ notification = Mameapns::Notification.new(device_token: "<aa>")
7
+ notification.device_token.should eq "aa"
8
+ end
9
+
10
+ it "should strip spaces from the given string" do
11
+ notification = Mameapns::Notification.new(device_token: "a a a")
12
+ notification.device_token.should eq "aaa"
13
+ end
14
+
15
+ it "should default the sound to 1.aiff" do
16
+ Mameapns::Notification.new.sound.should eq "1.aiff"
17
+ end
18
+
19
+ it "should default the expiry to 1 day" do
20
+ Mameapns::Notification.new.expiry.should eq 86400
21
+ end
22
+
23
+ describe "#as_json" do
24
+ it "should include the alert if present" do
25
+ notification = Mameapns::Notification.new(alert: "aaa")
26
+ notification.as_json["aps"]["alert"].should eq "aaa"
27
+ end
28
+
29
+ it "should not include the alert key if the alert is not present" do
30
+ notification = Mameapns::Notification.new
31
+ notification.as_json["aps"].key?("alert").should be_false
32
+ end
33
+
34
+ it "should include the badge if present" do
35
+ notification = Mameapns::Notification.new(badge: 6)
36
+ notification.as_json["aps"]["badge"].should eq 6
37
+ end
38
+
39
+ it "should not include the badge key if the badge is not present" do
40
+ notification = Mameapns::Notification.new
41
+ notification.as_json["aps"].key?("badge").should be_false
42
+ end
43
+
44
+ it "should include attributes for the device" do
45
+ notification = Mameapns::Notification.new
46
+ notification.attributes_for_device = { "koko" => "hore", "wan" => "wan" }
47
+ notification.as_json["aps"]["koko"].should eq "hore"
48
+ notification.as_json["aps"]["wan"].should eq "wan"
49
+ end
50
+ end
51
+
52
+ describe "#to_binary" do
53
+ it "should correctly convert the notification to binary" do
54
+ notification = Mameapns::Notification.new({
55
+ identifier: 37,
56
+ alert: "abc",
57
+ device_token: "79e9b418e64ee99c4236a2cf5270e6b3421b8e2672a97670888829abe529c5e4"
58
+ })
59
+
60
+ notification.to_binary.should eq "\x01\x00\x00\x00%\x00\x01Q\x80\x00 y\xE9\xB4\x18\xE6N\xE9\x9CB6\xA2\xCFRp\xE6\xB3B\e\x8E&r\xA9vp\x88\x88)\xAB\xE5)\xC5\xE4\x00({\"aps\":{\"alert\":\"abc\",\"sound\":\"1.aiff\"}}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,6 @@
1
+ $:.unshift File.expand_path(__FILE__, "../lib")
2
+
3
+ require "mameapns"
4
+
5
+ RSpec.configure do |c|
6
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mameapns
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - sutetotanuki
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: guard-rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: growl
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rb-fsevent
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.9.1
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.9.1
94
+ description: mame apns
95
+ email:
96
+ - sutetotanuki@gmail.com
97
+ executables:
98
+ - mameapns
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - .gitignore
103
+ - Gemfile
104
+ - Guardfile
105
+ - LICENSE.txt
106
+ - README.md
107
+ - Rakefile
108
+ - bin/mameapns
109
+ - lib/mameapns.rb
110
+ - lib/mameapns/application.rb
111
+ - lib/mameapns/connection.rb
112
+ - lib/mameapns/error.rb
113
+ - lib/mameapns/interruptible_sleep.rb
114
+ - lib/mameapns/notification.rb
115
+ - lib/mameapns/options.rb
116
+ - lib/mameapns/session.rb
117
+ - lib/mameapns/session/deliver.rb
118
+ - lib/mameapns/session/feedback.rb
119
+ - lib/mameapns/version.rb
120
+ - mameapns.gemspec
121
+ - spec/mameapns/notification_spec.rb
122
+ - spec/spec_helper.rb
123
+ homepage: ''
124
+ licenses: []
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 1.8.23
144
+ signing_key:
145
+ specification_version: 3
146
+ summary: mame apns
147
+ test_files:
148
+ - spec/mameapns/notification_spec.rb
149
+ - spec/spec_helper.rb