lib/ass.rb in ass-0.0.21 vs lib/ass.rb in ass-0.0.24

- old
+ new

@@ -1,406 +1,4 @@ #encoding: utf-8 -require 'rubygems' -require 'sinatra' -require 'sequel' -require 'socket' -require 'openssl' -require 'cgi' -require 'rufus/scheduler' -require 'eventmachine' -require 'sinatra/base' -require 'rack/mobile-detect' -require 'yaml' -require 'uri-handler' -require 'net/http' -require 'active_support' -require 'json' -require 'digest/sha2' -require 'will_paginate' -require 'will_paginate/sequel' # or data_mapper/sequel -require 'uri' -require 'sinatra/reloader' if development? -require 'sinatra/synchrony' - -############################################################ -## Initilization Setup -############################################################ -LIBDIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) -ROOTDIR = File.expand_path(File.join(File.dirname(__FILE__), '..')) -unless $LOAD_PATH.include?(LIBDIR) - $LOAD_PATH << LIBDIR -end - -unless File.exist?("#{Dir.pwd}/ass.yml") then - puts 'create config file: ass.yml' - system "cp #{ROOTDIR}/ass.yml #{Dir.pwd}/ass.yml" -end - -unless File.exist?("#{Dir.pwd}/cron") then - puts "create a demo 'cron' script" - system "cp #{ROOTDIR}/cron #{Dir.pwd}/cron" -end - -############################################################ -## Configuration Setup -############################################################ -env = ENV['SINATRA_ENV'] || "development" -config = YAML.load_file("#{Dir.pwd}/ass.yml") -$timer = "#{config['timer']}".to_i -$cron = config['cron'] || 'cron' -$port = "#{config['port']}".to_i || 4567 -$mode = config['mode'] || env -$VERSION = File.open("#{ROOTDIR}/VERSION", "rb").read -$apps = config['apps'] || [] -$log = config['log'] || 'off' -$user = config['user'] || 'admin' -$pass = config['pass'] || 'pass' -$flood = "#{config['flood']}".to_i || 1 # default 1 minute - -$client_ip = '127.0.0.1' -$last_access = 0 - -############################################################ -## Certificate Key Setup -############################################################ - -$certkey = {} - -def check_cert - $apps.each { |app| - unless File.exist?("#{Dir.pwd}/#{app}_#{$mode}.pem") then - puts "Please provide #{app}_#{$mode}.pem under '#{Dir.pwd}/' directory" - return false; - else - puts "'#{app}'s #{$mode} PEM: (#{app}_#{$mode}.pem)" - certfile = File.read("#{Dir.pwd}/#{app}_#{$mode}.pem") - openSSLContext = OpenSSL::SSL::SSLContext.new - openSSLContext.cert = OpenSSL::X509::Certificate.new(certfile) - openSSLContext.key = OpenSSL::PKey::RSA.new(certfile) - $certkey["#{app}"] = openSSLContext - end - } - return true -end - -unless check_cert then - html = <<-END -1: please provide certificate key pem file under current directory, name should be: appid_development.pem for development and appid_production.pem for production -2: edit your ass.yml under current directory -3: run ass -4: iOS Client: in AppDelegate file, didRegisterForRemoteNotificationsWithDeviceToken method should access url below: - END - $apps.each { |app| - html << "'#{app}'s registration url: http://serverIP:#{$port}/v1/apps/#{app}/DeviceToken" - } - html << "5: Server: cron should access 'curl http://localhost:#{$port}/v1/app/push/{messages}/{pid}' to send push message" - puts html - exit -else - html = <<-END -#{'*'*80} -Apple Service Server(#{$VERSION}) is Running ... -Push Notification Service: Enabled -Mode: #{$mode} -Port: #{$port} - END - html << "#{'*'*80}" - html << "Cron Job: '#{Dir.pwd}/#{$cron}' script is running every #{$timer} #{($timer == 1) ? 'minute' : 'minutes'} " unless "#{$timer}".to_i == 0 - html << "\n" - html << "access http://localhost:#{$port}/ for more information" - html << "\n" - html << "#{'*'*80}" - puts html -end - -############################################################ -## Sequel Database Setup -############################################################ - -unless File.exist?("#{Dir.pwd}/ass-#{$mode}.db") then - $DB = Sequel.connect("sqlite://#{Dir.pwd}/ass-#{$mode}.db") - $DB.create_table :tokens do - primary_key :id - String :app, :unique => false, :null => false - String :token, :unique => false, :null => false, :size => 100 - Time :created_at - index [:app, :token] - end - $DB.create_table :pushes do - primary_key :id - String :pid, :unique => false, :null => false, :size => 100 - String :app, :unique => false, :null => false, :size => 30 - String :message, :unique => false, :null => false, :size => 107 - Time :created_at - index [:pid, :app, :message] - end -else - $DB = Sequel.connect("sqlite://#{Dir.pwd}/ass-#{$mode}.db") -end - -WillPaginate.per_page = 10 - -class Token < Sequel::Model - Sequel.extension :pagination -end - -class Push < Sequel::Model - Sequel.extension :pagination -end - -############################################################ -## Timer Job Setup -############################################################ -scheduler = Rufus::Scheduler.start_new - -unless $timer == 0 then - scheduler.every "#{$timer}m" do - puts "running job: '#{Dir.pwd}/#{$cron}' every #{$timer} #{($timer == 1) ? 'minute' : 'minutes'}" - system "./#{$cron}" - end -end - -############################################################ -## Apple Service Server based on Sinatra -############################################################ - -class App < Sinatra::Base - - register Sinatra::Synchrony - - use Rack::MobileDetect - - set :root, File.expand_path('../../', __FILE__) - set :port, "#{$port}".to_i - set :public_folder, File.dirname(__FILE__) + '/../public' - set :views, File.dirname(__FILE__) + '/../views' - - helpers do - - def checkFlood?(req) - if $client_ip != "#{req.ip}" then - $client_ip = "#{req.ip}" - return false - else - if $last_access == 0 then - return false - else - return isFlood? - end - end - end - - def isFlood? - result = (Time.now - $last_access) < $flood * 60 - $last_access = Time.now - return result - end - - def iOS? - result = case request.env['X_MOBILE_DEVICE'] - when /iPhone|iPod|iPad/ then - true - else false - end - return result - end - - def authorized? - @auth ||= Rack::Auth::Basic::Request.new(request.env) - @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == ["#{$user}", "#{$pass}"] - end - - def protected! - unless authorized? - response['WWW-Authenticate'] = %(Basic realm="Restricted Area") - throw(:halt, [401, "Oops... we need your login name & password\n"]) - end - end - end - - configure :production, :development do - if "#{$log}".strip == 'on' then - enable :logging - end - end - - if "#{$mode}".strip == 'development' then - set :show_exceptions, true - set :dump_errors, true - else - set :show_exceptions, false - set :dump_errors, false - end - - get '/' do - erb :index - end - - get '/about' do - erb :about - end - - not_found do - erb :not_found - end - - error do - @error = ""; - @error = params['captures'].first.inspect if "#{$mode}".strip == 'development' - end - - post '/v1/send' do - app = params[:app] - message = CGI::escape(params[:message] || "") - pid = "#{Time.now.to_i}" - # begin - # url = URI.parse("http://localhost:#{$port}/v1/apps/#{app}/push") - # post_args1 = { :alert => "#{message}".encode('UTF-8'), :pid => "#{pid}" } - # Net::HTTP.post_form(url, post_args1) - # rescue =>err - # puts "#{err.class} ##{err}" - # end - system "curl http://localhost:#{$port}/v1/apps/#{app}/push/#{message}/#{pid}" - redirect '/v1/admin/push' if (params[:app] and params[:message]) - end - - get "/v1/admin/:db" do - protected! - db = params[:db] || 'token' - page = 1 - page = params[:page].to_i if params[:page] - if (db == 'token') then - @o = [] - $apps.each_with_index { |app, index| - @o << Token.where(:app => app).order(:id).reverse.paginate(page, 20) - } - erb :token - elsif (db == 'push') then - @p = [] - $apps.each_with_index { |app, index| - @p << Push.where(:app => app).order(:id).reverse.paginate(page, 20) - } - erb :push - else - erb :not_found - end - end - - $apps.each { |app| - - ## register token api - get "/v1/apps/#{app}/:token" do - if (("#{params[:token]}".length == 64) and iOS? and checkFlood?(request) ) then - puts "[#{params[:token]}] was added to '#{app}'" if "#{$mode}".strip == 'development' - o = Token.first(:app => app, :token => params[:token]) - unless o - Token.insert( - :app => app, - :token => params[:token], - :created_at => Time.now - ) - end - end - end - - ## http POST method push api - post "/v1/apps/#{app}/push" do - protected! unless request.host == 'localhost' - message = CGI::unescape(params[:alert] || "")[0..107] - badge = 1 - puts "params[:badge] = [#{params[:badge]}]" - badge = params[:badge].to_i if params[:badge] and params[:badge] != '' - sound = CGI::unescape(params[:sound] || "") - extra = CGI::unescape(params[:extra] || "") - - puts "#{badge} : #{message} extra: #{extra}" if "#{$mode}".strip == 'development' - pid = params[:pid] - - puts "'#{message}' was sent to (#{app}) with pid: [#{pid}], badge:#{badge} , sound: #{sound}, extra:#{extra}" if "#{$mode}".strip == 'development' - - @tokens = Token.where(:app => "#{app}") - @exist = Push.first(:pid => "#{pid}", :app => "#{app}") - - unless @exist - - Push.insert(:pid => pid, :message => message, :created_at => Time.now, :app => "#{app}" ) - - openSSLContext = $certkey["#{app}"] - # Connect to port 2195 on the server. - sock = nil - if $mode == 'production' then - sock = TCPSocket.new('gateway.push.apple.com', 2195) - else - sock = TCPSocket.new('gateway.sandbox.push.apple.com', 2195) - end - # do our SSL handshaking - sslSocket = OpenSSL::SSL::SSLSocket.new(sock, openSSLContext) - sslSocket.connect - - # write our packet to the stream - @tokens.each do |o| - tokenText = o[:token] - # pack the token to convert the ascii representation back to binary - tokenData = [tokenText].pack('H*') - # construct the payload - po = {:aps => {:alert => "#{message}", :badge => badge, :sound => "#{sound}"}, :extra => "#{extra}"} - payload = ActiveSupport::JSON.encode(po) - # construct the packet - packet = [0, 0, 32, tokenData, 0, payload.length, payload].pack("ccca*cca*") - # read our certificate and set up our SSL context - sslSocket.write(packet) - end - # cleanup - sslSocket.close - sock.close - end - end - - ## http GET method push api - get "/v1/apps/#{app}/push/:message/:pid" do - protected! unless request.host == 'localhost' - message = CGI::unescape(params[:message]) - puts message if "#{$mode}".strip == 'development' - pid = params[:pid] - - puts "'#{message}' was sent to (#{app}) with pid: [#{pid}]" if "#{$mode}".strip == 'development' - - @tokens = Token.where(:app => "#{app}") - @exist = Push.first(:pid => "#{pid}", :app => "#{app}") - - unless @exist - - Push.insert(:pid => pid, :message => message, :created_at => Time.now, :app => "#{app}" ) - - openSSLContext = $certkey["#{app}"] - # Connect to port 2195 on the server. - sock = nil - if $mode == 'production' then - sock = TCPSocket.new('gateway.push.apple.com', 2195) - else - sock = TCPSocket.new('gateway.sandbox.push.apple.com', 2195) - end - # do our SSL handshaking - sslSocket = OpenSSL::SSL::SSLSocket.new(sock, openSSLContext) - sslSocket.connect - - # write our packet to the stream - @tokens.each do |o| - tokenText = o[:token] - # pack the token to convert the ascii representation back to binary - tokenData = [tokenText].pack('H*') - # construct the payload - po = {:aps => {:alert => "#{message}", :badge => 1}} - payload = ActiveSupport::JSON.encode(po) - # construct the packet - packet = [0, 0, 32, tokenData, 0, payload.length, payload].pack("ccca*cca*") - # read our certificate and set up our SSL context - sslSocket.write(packet) - end - # cleanup - sslSocket.close - sock.close - end - end - } -end +require 'ass/conf' +require 'ass/app' \ No newline at end of file