以下のサイトを参考にして構築しました。
【iPhone】Push Notificationの実装方法
作成するのはモデルです。
notification_provider.rbを作成します。
# coding: ascii-8bit
require "openssl"
require "socket"
class NotificationProvider
include ActionView::Helpers::JavaScriptHelper
HOST = case Rails.env
# when "test" then "gateway.sandbox.push.apple.com"
when "development" then "gateway.sandbox.push.apple.com"
when "production" then "gateway.push.apple.com"
end
PORT = 2195
CERT_FILE = Rails.root.join("config/鍵名.cert")
RSA_KEY_FILE = Rails.root.join("config/鍵名.key")
attr_reader :ssl
以上の様にします。require "openssl"
require "socket"
class NotificationProvider
include ActionView::Helpers::JavaScriptHelper
HOST = case Rails.env
# when "test" then "gateway.sandbox.push.apple.com"
when "development" then "gateway.sandbox.push.apple.com"
when "production" then "gateway.push.apple.com"
end
PORT = 2195
CERT_FILE = Rails.root.join("config/鍵名.cert")
RSA_KEY_FILE = Rails.root.join("config/鍵名.key")
attr_reader :ssl
「CERT_FILE」と「RSA_KEY_FILE」が出てきたのでこちらを作成します。
まずは作成した鍵(pem)を/configの直下に置きます。
そしてその鍵内上の
「-----BEGIN CERTIFICATE-----」から「-----END CERTIFICATE-----」を全て
をコピーして「〜.cert」として作りpem鍵と同じ場所に置きます。
同じくその下の
「-----BEGIN RSA PRIVATE KEY-----」から「-----END RSA PRIVATE KEY-----」
を全てコピーして「〜.key」として作りpem鍵と同じ場所に置きます。
これで準備OK!さぁ作りましょう〜
続いて同じnotification_provider.rbに追記します。
class << self
def open
provider = self.new
provider.open
provider
end
end
def open
return if @ssl
if HOST
@socket = TCPSocket.new(HOST, PORT)
context = OpenSSL::SSL::SSLContext.new("SSLv3")
context.cert = OpenSSL::X509::Certificate.new(File.read(CERT_FILE))
context.key = OpenSSL::PKey::RSA.new(File.read(RSA_KEY_FILE))
@ssl = OpenSSL::SSL::SSLSocket.new(@socket, context)
@ssl.connect
else
@ssl = DummyServer.new
end
end
def close
@ssl.close if @ssl
@socket.close if @socket
@ssl = @socket = nil
end
def closed?
@ssl ? @ssl.closed? : true
end
def message(identifier, expired_at, token, alert)
alert = j(alert.dup)
alert.force_encoding("ascii-8bit") if RUBY_VERSION.to_f >= 1.9
payload = %Q!{ "aps":{ "alert":"#{alert}", "sound":"default" } }!
"\x01" + # 常に1
[identifier].pack("N") + # 識別子、4バイト
[expired_at.to_i].pack("N") + # 期限、4バイト(big endian)
"\x00\x20" + # トークン長、2バイト、常に32
token + # デバイストークン
[payload.size].pack("n") + # ペイロード長、2バイト
payload # ペイロード
end
def notify(device_token, content,
expired_at = 1.days.from_now, logger = Rails.logger)
raise "Provider is not opened!" unless @ssl
request = message(0, expired_at, device_token.token, content)
response = nil
@ssl.write(request)
if @ssl.kind_of? OpenSSL::SSL::SSLSocket
if IO.select([@ssl], nil, nil, 0.5)
response = @ssl.read(6)
end
else
response = @ssl.read(6)
end
if response.present?
arr = response.unpack("ccN") # 1バイト目:8、2バイト目:エラーコード
if arr[0] == 8
logger.info("error code: #{arr[1]}, divece token id: #{device_token.id}")
else
logger.info("unknown error, divece token id: #{device_token.id}")
end
return false
end
true
end
これはもう定型文でいいかもしれません。
def open
provider = self.new
provider.open
provider
end
end
def open
return if @ssl
if HOST
@socket = TCPSocket.new(HOST, PORT)
context = OpenSSL::SSL::SSLContext.new("SSLv3")
context.cert = OpenSSL::X509::Certificate.new(File.read(CERT_FILE))
context.key = OpenSSL::PKey::RSA.new(File.read(RSA_KEY_FILE))
@ssl = OpenSSL::SSL::SSLSocket.new(@socket, context)
@ssl.connect
else
@ssl = DummyServer.new
end
end
def close
@ssl.close if @ssl
@socket.close if @socket
@ssl = @socket = nil
end
def closed?
@ssl ? @ssl.closed? : true
end
def message(identifier, expired_at, token, alert)
alert = j(alert.dup)
alert.force_encoding("ascii-8bit") if RUBY_VERSION.to_f >= 1.9
payload = %Q!{ "aps":{ "alert":"#{alert}", "sound":"default" } }!
"\x01" + # 常に1
[identifier].pack("N") + # 識別子、4バイト
[expired_at.to_i].pack("N") + # 期限、4バイト(big endian)
"\x00\x20" + # トークン長、2バイト、常に32
token + # デバイストークン
[payload.size].pack("n") + # ペイロード長、2バイト
payload # ペイロード
end
def notify(device_token, content,
expired_at = 1.days.from_now, logger = Rails.logger)
raise "Provider is not opened!" unless @ssl
request = message(0, expired_at, device_token.token, content)
response = nil
@ssl.write(request)
if @ssl.kind_of? OpenSSL::SSL::SSLSocket
if IO.select([@ssl], nil, nil, 0.5)
response = @ssl.read(6)
end
else
response = @ssl.read(6)
end
if response.present?
arr = response.unpack("ccN") # 1バイト目:8、2バイト目:エラーコード
if arr[0] == 8
logger.info("error code: #{arr[1]}, divece token id: #{device_token.id}")
else
logger.info("unknown error, divece token id: #{device_token.id}")
end
return false
end
true
end
さぁあとはcronスクリプトを作成する必要がある。
とりあえずcronスクリプトは置いておいてcronスクリプトを動かすための構築をします。
今回はお知らせを表示するので「announcemnet」を編集します。
def display_status
{ nil => "未送信", "delivered" => "送信済み", "delivering" => "送信中" }[status]
end
def not_delivered?
status.nil?
end
def delivered?
status == "delivered"
end
def delivering?
status == "delivering"
end
def update_directly(updates)
assign_attributes(updates, :without_protection => true)
Announcement.update_all(updates, :id => self.id)
end
def deliver
return unless not_delivered?
update_directly(:status => "delivering")
provider = NotificationProvider.open
success = 0
failure = 0
total = DeviceToken.count
DeviceToken.all.each do |dt|
if provider.notify(dt, self.content, どれくらい送り続けるかの日数, self)
success += 1
else
failure += 1
end
if provider.closed?
info("APN Server closed unexpectedly!")
break
end
end
provider.close
info("total: #{total}, success: #{success}, failure: #{failure}.")
update_directly(:log => self.log, :status => "delivered")
end
def info(text)
self.log = "" if log.blank?
self.log += Time.now.strftime("%Y-%m-%d %H:%M:%S ") + text + "\n"
# cronログを作成している
end
class << self
def deliver_all
DeviceToken.where("updated_at < ?", デバイストークンの有効期限).delete_all
# デバイストークンをupdateしていない場合削除される
announcements = active.not_delivered.past
count = announcements.count
announcements.each do |ann|
ann.deliver
end
Rails.logger.info "executed delivered_all, #{count}"
end
end
以上の内容でお知らせをAPNsを使ってiPhoneやiPadでのアプリに表示させることが出来る。{ nil => "未送信", "delivered" => "送信済み", "delivering" => "送信中" }[status]
end
def not_delivered?
status.nil?
end
def delivered?
status == "delivered"
end
def delivering?
status == "delivering"
end
def update_directly(updates)
assign_attributes(updates, :without_protection => true)
Announcement.update_all(updates, :id => self.id)
end
def deliver
return unless not_delivered?
update_directly(:status => "delivering")
provider = NotificationProvider.open
success = 0
failure = 0
total = DeviceToken.count
DeviceToken.all.each do |dt|
if provider.notify(dt, self.content, どれくらい送り続けるかの日数, self)
success += 1
else
failure += 1
end
if provider.closed?
info("APN Server closed unexpectedly!")
break
end
end
provider.close
info("total: #{total}, success: #{success}, failure: #{failure}.")
update_directly(:log => self.log, :status => "delivered")
end
def info(text)
self.log = "" if log.blank?
self.log += Time.now.strftime("%Y-%m-%d %H:%M:%S ") + text + "\n"
# cronログを作成している
end
class << self
def deliver_all
DeviceToken.where("updated_at < ?", デバイストークンの有効期限).delete_all
# デバイストークンをupdateしていない場合削除される
announcements = active.not_delivered.past
count = announcements.count
announcements.each do |ann|
ann.deliver
end
Rails.logger.info "executed delivered_all, #{count}"
end
end
ほぼ定型文で保存かな?
0 件のコメント:
コメントを投稿