grocer-pushpackager 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dd2d3abb604660531db0cd55d1d7344028266a36
4
+ data.tar.gz: fe2f900f5262f35434e1822661cea84d4ba5ae0f
5
+ SHA512:
6
+ metadata.gz: 001ebdf1bf63f8bbefb4153fac23b0cda17bef0d0aeaf6513e646a91cc9f1fb2b39b10a55fe9e0d4e3046d1da348b7d413ddb435bac6d5f8a3311ce8d9330ce9
7
+ data.tar.gz: a9b7748647b89a8699c277c475a270fe142765f6eeb1fd01376922ce2e8f71a891ee87e44abaae7fd0afc22e453fb936001b5e901feac20fcccce36fe801baa9
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1 @@
1
+ pushpackager
@@ -0,0 +1 @@
1
+ 2.0.0-p195
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ script: rspec spec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in grocer-pushpackager.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Kurt Nelson
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.
@@ -0,0 +1,33 @@
1
+ # Grocer::Pushpackager
2
+ [![Build Status](https://secure.travis-ci.org/grocer/grocer-pushpackager.png)](http://travis-ci.org/grocer/grocer-pushpackager)
3
+ [![Gem Version](https://badge.fury.io/rb/grocer-pushpackager.png)](http://badge.fury.io/rb/grocer-pushpackager)
4
+ [![Code Climate](https://codeclimate.com/github/grocer/grocer-pushpackager.png)](https://codeclimate.com/github/grocer/grocer-pushpackager)
5
+ [![Coverage Status](https://coveralls.io/repos/grocer/grocer-pushpackager/badge.png?branch=master)](https://coveralls.io/r/grocer/grocer-pushpackager)
6
+ [Documentation](http://rubydoc.info/gems/grocer-pushpackager/)
7
+
8
+ Builds a PushPackage for Apple's APN for use with Safari/Mavericks
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'grocer-pushpackager'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install grocer-pushpackager
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'grocer/pushpackager/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "grocer-pushpackager"
8
+ spec.version = Grocer::Pushpackager::VERSION
9
+ spec.authors = ["Kurt Nelson"]
10
+ spec.email = ["kurtisnelson@gmail.com"]
11
+ spec.description = %q{Builds push packages for OS X Mavericks web notifications}
12
+ spec.summary = <<-DESC
13
+ To use push notifications to Safari/OS X with the APN service,
14
+ you must build a signed push package that is generated by your web server
15
+ for every user. This gem handles building that package.
16
+ DESC
17
+ spec.homepage = "https://github.com/grocer/grocer-pushpackager"
18
+ spec.license = "MIT"
19
+
20
+ spec.files = `git ls-files`.split($/)
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'coveralls'
28
+ spec.add_development_dependency 'rspec'
29
+ spec.add_development_dependency 'rake'
30
+ spec.add_dependency 'rubyzip'
31
+ end
@@ -0,0 +1,2 @@
1
+ require "grocer/pushpackager/version"
2
+ require "grocer/pushpackager/package"
@@ -0,0 +1,23 @@
1
+ module Grocer
2
+ module Pushpackager
3
+ class Icon
4
+ attr_reader :name, :contents
5
+ def initialize(size)
6
+ @name = "icon_#{size}.png"
7
+ self
8
+ end
9
+
10
+ def from_file file
11
+ @contents = file.read
12
+ file.close
13
+ self
14
+ end
15
+
16
+ def valid?
17
+ raise ArgumentError, "invalid icon name" unless @name
18
+ raise ArgumentError, "missing contents" unless @contents
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ require 'forwardable'
2
+ require_relative 'icon'
3
+
4
+ module Grocer
5
+ module Pushpackager
6
+ class IconSet
7
+ include Enumerable
8
+ extend Forwardable
9
+ def_delegators :@icons, :each, :<<
10
+
11
+ def initialize(config = {})
12
+ raise ArgumentError, "Missing icon set" unless config[:iconSet]
13
+ @icons = []
14
+ config[:iconSet].each do |size, file|
15
+ @icons << Icon.new(size).from_file(file)
16
+ end
17
+ end
18
+
19
+ def valid?
20
+ fail unless @icons.select{|i| i.valid?}.count == @icons.count
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,84 @@
1
+ require 'zip/zip'
2
+ require 'digest/sha1'
3
+ require 'openssl'
4
+ require_relative 'icon_set'
5
+ require_relative 'website'
6
+
7
+ module Grocer
8
+ module Pushpackager
9
+ class Package
10
+ attr_accessor :key, :certificate, :icon_set, :website
11
+
12
+ def initialize(config = {})
13
+ @icon_set = IconSet.new(config)
14
+ @website = Website.new(config)
15
+ @certificate = config[:certificate]
16
+ @key = config[:key]
17
+ end
18
+
19
+ def authentication_token= value
20
+ @website.authentication_token = value
21
+ @signature = nil
22
+ @website_json = nil
23
+ @manifest_json = nil
24
+ end
25
+
26
+ def valid?
27
+ @icon_set.valid?
28
+ @website.valid?
29
+ raise ArgumentError, "Missing private key" unless @key
30
+ raise ArgumentError, "Missing certificate" unless @certificate
31
+ true
32
+ end
33
+
34
+ def file
35
+ raise unless self.valid?
36
+ package = Tempfile.new('package')
37
+ package.write(build_zip.string)
38
+ package.close
39
+ package
40
+ end
41
+
42
+ private
43
+ def website_json
44
+ @website_json ||= @website.to_json
45
+ end
46
+
47
+ def manifest_json
48
+ return @manifest_json if @manifest_json
49
+ @manifest_json = {
50
+ :"website.json" => Digest::SHA1.hexdigest(website_json)
51
+ }.merge(icon_set_manifest).to_json
52
+ end
53
+
54
+ def icon_set_manifest
55
+ manifest = { }
56
+ @icon_set.each do |icon|
57
+ manifest[:"icon.iconset\/#{icon.name}"] = Digest::SHA1.hexdigest(icon.contents)
58
+ end
59
+ manifest
60
+ end
61
+
62
+ def signature
63
+ return @signature if @signature
64
+ @signature = OpenSSL::PKCS7::sign(@certificate, @key, manifest_json, [], OpenSSL::PKCS7::DETACHED)
65
+ end
66
+
67
+ def build_zip
68
+ buffer = Zip::ZipOutputStream.write_buffer do |out|
69
+ @icon_set.each do |icon|
70
+ out.put_next_entry("icon.iconset/#{icon.name}")
71
+ out.write icon.contents
72
+ end
73
+ out.put_next_entry('website.json')
74
+ out.write website_json
75
+ out.put_next_entry('manifest.json')
76
+ out.write manifest_json
77
+ out.put_next_entry('signature')
78
+ out.write signature.to_der
79
+ end
80
+ buffer
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,5 @@
1
+ module Grocer
2
+ module Pushpackager
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,44 @@
1
+ require 'uri'
2
+ require 'json'
3
+
4
+ module Grocer
5
+ module Pushpackager
6
+ class Website
7
+ attr_accessor :authentication_token
8
+ attr_reader :website_name, :push_id, :allowed_domains, :url_format_string, :web_service_url
9
+
10
+ def initialize(config = {})
11
+ @website_name = config[:websiteName]
12
+ @push_id = config[:websitePushID]
13
+ @allowed_domains = []
14
+ if config[:allowedDomains]
15
+ @allowed_domains = config[:allowedDomains].map{|domain| URI(domain)}
16
+ end
17
+ @url_format_string = config[:urlFormatString]
18
+ @web_service_url = URI(config[:webServiceURL])
19
+ @authentication_token = config[:authenticationToken]
20
+ end
21
+
22
+ def valid?
23
+ raise ArgumentError if not @website_name or @website_name.empty?
24
+ raise ArgumentError if not @push_id or @push_id.empty?
25
+ raise ArgumentError if not @url_format_string or @url_format_string.empty?
26
+ raise ArgumentError if not @web_service_url or @web_service_url.to_s.empty? or not ['http', 'https'].include?(@web_service_url.scheme)
27
+ raise ArgumentError if not @authentication_token or @authentication_token.empty?
28
+ raise ArgumentError, "Allowed domains" unless @allowed_domains.count > 0
29
+ true
30
+ end
31
+
32
+ def to_json
33
+ raise "Invalid website" unless valid?
34
+ { websiteName: @website_name.to_s,
35
+ websitePushID: @push_id.to_s,
36
+ allowedDomains: @allowed_domains.map{|domain| domain.to_s},
37
+ urlFormatString: @url_format_string.to_s,
38
+ authenticationToken: @authentication_token.to_s,
39
+ webServiceURL: @web_service_url.to_s,
40
+ }.to_json
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grocer::Pushpackager::Package do
4
+ subject {Grocer::Pushpackager::Package}
5
+ it "can be initialized as a valid object" do
6
+ package = subject.new({
7
+ websiteName: "Bay Airlines",
8
+ websitePushID: "web.com.example.domain",
9
+ allowedDomains: ["http://domain.example.com"],
10
+ urlFormatString: "http://domain.example.com/%@/?flight=%@",
11
+ webServiceURL: "https://example.com/push",
12
+ iconSet: { },
13
+ certificate: '',
14
+ key: ''
15
+ })
16
+ expect{package.valid?}.to raise_error ArgumentError
17
+ package.authentication_token = "19f8d7a6e9fb8a7f6d9330dabe"
18
+ package.should be_valid
19
+ end
20
+
21
+ context "complete, valid package" do
22
+ before :all do
23
+ @pair = test_ssl_pair
24
+ end
25
+
26
+ subject do
27
+ Grocer::Pushpackager::Package.new({
28
+ websiteName: "Bay Airlines",
29
+ websitePushID: "web.com.example.domain",
30
+ allowedDomains: ["http://domain.example.com"],
31
+ urlFormatString: "http://domain.example.com/%@/?flight=%@",
32
+ authenticationToken: "19f8d7a6e9fb8a7f6d9330dabe",
33
+ webServiceURL: "https://example.com/push",
34
+ certificate: @pair[:certificate],
35
+ key: @pair[:key],
36
+ iconSet: {
37
+ :'16x16' => test_icon,
38
+ :'16x16@2x' => test_icon,
39
+ :'32x32' => test_icon,
40
+ :'32x32@2x' => test_icon,
41
+ :'128x128' => test_icon,
42
+ :'128x128@2x' => test_icon
43
+ }
44
+ })
45
+ end
46
+
47
+ it{should be_valid}
48
+
49
+ it "generates a non-zero file" do
50
+ subject.send(:build_zip).string.should have_at_least(8).length
51
+ end
52
+
53
+ it "writes out file" do
54
+ package = subject.file
55
+ file = File.new('tmp/package.zip', 'w')
56
+ file.write(package.open.read)
57
+ package.unlink
58
+ file.should have_at_least(1).length
59
+ file.close
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'coveralls'
4
+ Coveralls.wear!
5
+ require 'pry'
6
+ require 'rspec'
7
+
8
+ require 'grocer/pushpackager'
9
+
10
+ # Requires supporting files with custom matchers and macros, etc,
11
+ # # in ./support/ and its subdirectories.
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
13
+ RSpec.configure do |config|
14
+
15
+ end
@@ -0,0 +1,25 @@
1
+ def test_icon
2
+ file = Tempfile.new('icon')
3
+ file.write("hello world")
4
+ file.rewind
5
+ file.unlink
6
+ file
7
+ end
8
+
9
+ def test_ssl_pair
10
+ key = OpenSSL::PKey::RSA.new(512)
11
+ name = OpenSSL::X509::Name.parse("CN=auralis/DC=topalis/DC=com")
12
+ cert = OpenSSL::X509::Certificate.new()
13
+ cert.version = 2
14
+ cert.serial = 0
15
+ cert.not_before = Time.new()
16
+ cert.not_after = cert.not_before + (60*60*24*365)
17
+ cert.public_key = key.public_key
18
+ cert.subject = name
19
+
20
+ # Sign cert
21
+ cert.issuer = name
22
+ cert.sign(key, OpenSSL::Digest::SHA1.new())
23
+
24
+ {certificate: cert, key: key}
25
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grocer-pushpackager
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Kurt Nelson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coveralls
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubyzip
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Builds push packages for OS X Mavericks web notifications
98
+ email:
99
+ - kurtisnelson@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .document
105
+ - .gitignore
106
+ - .rspec
107
+ - .ruby-gemset
108
+ - .ruby-version
109
+ - .travis.yml
110
+ - Gemfile
111
+ - LICENSE.txt
112
+ - README.md
113
+ - Rakefile
114
+ - grocer-pushpackager.gemspec
115
+ - lib/grocer/pushpackager.rb
116
+ - lib/grocer/pushpackager/icon.rb
117
+ - lib/grocer/pushpackager/icon_set.rb
118
+ - lib/grocer/pushpackager/package.rb
119
+ - lib/grocer/pushpackager/version.rb
120
+ - lib/grocer/pushpackager/website.rb
121
+ - spec/package_spec.rb
122
+ - spec/spec_helper.rb
123
+ - spec/support/test_files.rb
124
+ homepage: https://github.com/grocer/grocer-pushpackager
125
+ licenses:
126
+ - MIT
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.0.3
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: To use push notifications to Safari/OS X with the APN service, you must build
148
+ a signed push package that is generated by your web server for every user. This
149
+ gem handles building that package.
150
+ test_files:
151
+ - spec/package_spec.rb
152
+ - spec/spec_helper.rb
153
+ - spec/support/test_files.rb