webpush 0.1.6 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b5006a4c889349920fc642e5e126f9a2bb986fa8
4
- data.tar.gz: cc39101673ba4b9c52aa4482e1dbb554ccb51342
3
+ metadata.gz: 1e5f86e721e160b17bf21789f6c9d11a5d7ac297
4
+ data.tar.gz: 2f484b4300166b10772c1ebd9266228e9d4cd970
5
5
  SHA512:
6
- metadata.gz: 81560e7eabb65bac0fb4ce2972279654bdbcbca4719d815a40cd63994b20f59ba2430ad6835e6d5a0655df6897552344588948622399a405e9b4964bc2740b14
7
- data.tar.gz: 4785dc7d17f520a0df9891dd1ada08b57e59b19e972a9308190c3025ba7491f27052b0163c5f4dfd4fb7361066539bc98b325ef47e5e81bfa0bce0bca2309cc0
6
+ metadata.gz: 109c269ebbc2443613635450d497c1c1c7b5f24dffa96761c987bd77fb1fec1c252e7851a1dea19ee1e3d88fdd293ee58870408dc6391d0e82a6fbb6a3e1f6d4
7
+ data.tar.gz: ba9f2b0d91d175fca0e462870aa36366a2cbf4cde21b91acb729c36a6b55bf1c5fa6e50f7e2c13dec2bdc13128e0d4aa6e8a0853696493104e5da5c516bf2f64
data/.travis.yml CHANGED
@@ -2,3 +2,4 @@ language: ruby
2
2
  rvm:
3
3
  - 2.3.0
4
4
  before_install: gem install bundler -v 1.11.2
5
+ script: "bundle exec rake spec"
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # WebPush
2
2
 
3
+ [![Code Climate](https://codeclimate.com/github/zaru/webpush/badges/gpa.svg)](https://codeclimate.com/github/zaru/webpush)
4
+ [![Build Status](https://travis-ci.org/zaru/webpush.svg?branch=master)](https://travis-ci.org/zaru/webpush)
5
+
3
6
  This Gem will send the Web Push API. It supports the encryption necessary to payload.
4
7
 
5
8
  Payload is supported by Chrome50+, Firefox48+.
@@ -22,6 +25,8 @@ Or install it yourself as:
22
25
 
23
26
  ## Usage
24
27
 
28
+ ### using the payload
29
+
25
30
  ```ruby
26
31
  message = {
27
32
  title: "title",
@@ -30,14 +35,23 @@ message = {
30
35
  }
31
36
 
32
37
  Webpush.payload_send(
33
- message: JSON.generate(message),
34
38
  endpoint: "https://android.googleapis.com/gcm/send/eah7hak....",
39
+ message: JSON.generate(message),
35
40
  p256dh: "BO/aG9nYXNkZmFkc2ZmZHNmYWRzZmFl...",
36
41
  auth: "aW1hcmthcmFpa3V6ZQ==",
37
42
  api_key: "[GoogleDeveloper APIKEY]" # optional, not used in Firefox.
38
43
  )
39
44
  ```
40
45
 
46
+ ### not use the payload
47
+
48
+ ```ruby
49
+ Webpush.payload_send(
50
+ endpoint: "https://android.googleapis.com/gcm/send/eah7hak....",
51
+ api_key: "[GoogleDeveloper APIKEY]" # optional, not used in Firefox.
52
+ )
53
+ ```
54
+
41
55
  ### ServiceWorker sample
42
56
 
43
57
  see. https://github.com/zaru/web-push-sample
data/Rakefile CHANGED
@@ -3,4 +3,4 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
data/bin/rspec ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rspec-core", "rspec")
@@ -3,6 +3,8 @@ module Webpush
3
3
  extend self
4
4
 
5
5
  def encrypt(message, p256dh, auth)
6
+ assert_arguments(message, p256dh, auth)
7
+
6
8
  group_name = "prime256v1"
7
9
  salt = Random.new.bytes(16)
8
10
 
@@ -18,15 +20,15 @@ module Webpush
18
20
 
19
21
  client_auth_token = Base64.urlsafe_decode64(auth)
20
22
 
21
- prk = HKDF.new(shared_secret, :salt => client_auth_token, :algorithm => 'SHA256', :info => "Content-Encoding: auth\0").next_bytes(32)
23
+ prk = HKDF.new(shared_secret, salt: client_auth_token, algorithm: 'SHA256', info: "Content-Encoding: auth\0").next_bytes(32)
22
24
 
23
25
  context = create_context(client_public_key_bn, server_public_key_bn)
24
26
 
25
27
  content_encryption_key_info = create_info('aesgcm', context)
26
- content_encryption_key = HKDF.new(prk, :salt => salt, :info => content_encryption_key_info).next_bytes(16)
28
+ content_encryption_key = HKDF.new(prk, salt: salt, info: content_encryption_key_info).next_bytes(16)
27
29
 
28
30
  nonce_info = create_info('nonce', context)
29
- nonce = HKDF.new(prk, :salt => salt, :info => nonce_info).next_bytes(12)
31
+ nonce = HKDF.new(prk, salt: salt, info: nonce_info).next_bytes(12)
30
32
 
31
33
  ciphertext = encrypt_payload(message, content_encryption_key, nonce)
32
34
 
@@ -40,9 +42,9 @@ module Webpush
40
42
 
41
43
  private
42
44
 
43
- def create_context(clientPublicKey, serverPublicKey)
44
- c = convert16bit(clientPublicKey)
45
- s = convert16bit(serverPublicKey)
45
+ def create_context(client_public_key, server_public_key)
46
+ c = convert16bit(client_public_key)
47
+ s = convert16bit(server_public_key)
46
48
  context = "\0"
47
49
  context += [c.bytesize].pack("n*")
48
50
  context += c
@@ -77,5 +79,15 @@ module Webpush
77
79
  def convert16bit(key)
78
80
  [key.to_s(16)].pack("H*")
79
81
  end
82
+
83
+ def assert_arguments(message, p256dh, auth)
84
+ raise ArgumentError, "message cannot be blank" if blank?(message)
85
+ raise ArgumentError, "p256dh cannot be blank" if blank?(p256dh)
86
+ raise ArgumentError, "auth cannot be blank" if blank?(auth)
87
+ end
88
+
89
+ def blank?(value)
90
+ value.nil? || value.empty?
91
+ end
80
92
  end
81
93
  end
@@ -0,0 +1,75 @@
1
+ module Webpush
2
+ class Request
3
+ def initialize(endpoint, options = {})
4
+ @endpoint = endpoint
5
+ @options = default_options.merge(options)
6
+ @payload = @options.delete(:payload) || {}
7
+ end
8
+
9
+ def perform
10
+ uri = URI.parse(@endpoint)
11
+ http = Net::HTTP.new(uri.host, uri.port)
12
+ http.use_ssl = true
13
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
14
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
15
+ req.body = body
16
+ res = http.request(req)
17
+ res.code == "201"
18
+ rescue
19
+ false
20
+ end
21
+
22
+ def headers
23
+ headers = {}
24
+ headers["Content-Type"] = "application/octet-stream"
25
+ headers["Ttl"] = ttl
26
+
27
+ if encrypted_payload?
28
+ headers["Content-Encoding"] = "aesgcm"
29
+ headers["Encryption"] = "salt=#{salt_param}"
30
+ headers["Crypto-Key"] = "dh=#{dh_param}"
31
+ end
32
+
33
+ headers["Authorization"] = "key=#{api_key}" if api_key?
34
+
35
+ headers
36
+ end
37
+
38
+ def body
39
+ @payload.fetch(:ciphertext, "")
40
+ end
41
+
42
+ private
43
+
44
+ def ttl
45
+ @options.fetch(:ttl).to_s
46
+ end
47
+
48
+ def api_key
49
+ @options.fetch(:api_key, nil)
50
+ end
51
+
52
+ def api_key?
53
+ !(api_key.nil? || api_key.empty?)
54
+ end
55
+
56
+ def encrypted_payload?
57
+ [:ciphertext, :server_public_key_bn, :salt].all? { |key| @payload.has_key?(key) }
58
+ end
59
+
60
+ def dh_param
61
+ Base64.urlsafe_encode64(@payload.fetch(:server_public_key_bn)).delete('=')
62
+ end
63
+
64
+ def salt_param
65
+ Base64.urlsafe_encode64(@payload.fetch(:salt)).delete('=')
66
+ end
67
+
68
+ def default_options
69
+ {
70
+ api_key: nil,
71
+ ttl: 60*60*24*7*4 # 4 weeks
72
+ }
73
+ end
74
+ end
75
+ end
@@ -1,3 +1,3 @@
1
1
  module Webpush
2
- VERSION = "0.1.6"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/webpush.rb CHANGED
@@ -6,6 +6,7 @@ require 'json'
6
6
 
7
7
  require 'webpush/version'
8
8
  require 'webpush/encryption'
9
+ require 'webpush/request'
9
10
 
10
11
  module Webpush
11
12
 
@@ -14,36 +15,31 @@ module Webpush
14
15
  TEMP_GCM_URL = 'https://gcm-http.googleapis.com/gcm'
15
16
 
16
17
  class << self
17
- def payload_send(message:, endpoint:, p256dh:, auth:, api_key: "")
18
+ # Deliver the payload to the required endpoint given by the JavaScript
19
+ # PushSubscription. Including an optional message requires p256dh and
20
+ # auth keys from the PushSubscription.
21
+ #
22
+ # @param endpoint [String] the required PushSubscription url
23
+ # @param message [String] the optional payload
24
+ # @param p256dh [String] the user's public ECDH key given by the PushSubscription
25
+ # @param auth [String] the user's private ECDH key given by the PushSubscription
26
+ # @param options [Hash<Symbol,String>] additional options for the notification
27
+ # @option options [String] :api_key required for Google, omit for Firefox
28
+ # @option options [#to_s] :ttl Time-to-live in seconds
29
+ def payload_send(endpoint:, message: "", p256dh: "", auth: "", **options)
18
30
  endpoint = endpoint.gsub(GCM_URL, TEMP_GCM_URL)
19
31
 
20
- payload = Webpush::Encryption.encrypt(message, p256dh, auth)
21
- push_server_post(endpoint, payload, api_key)
32
+ payload = build_payload(message, p256dh, auth)
33
+
34
+ Webpush::Request.new(endpoint, options.merge(payload: payload)).perform
22
35
  end
23
36
 
24
37
  private
25
38
 
26
- def push_server_post(endpoint, payload, api_key = "")
27
- begin
28
- uri = URI.parse(endpoint)
29
- http = Net::HTTP.new(uri.host, uri.port)
30
- http.use_ssl = true
31
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
- header = {
33
- "Content-Type" => "application/octet-stream",
34
- "Content-Encoding" => "aesgcm",
35
- "Encryption" => "salt=#{Base64.urlsafe_encode64(payload[:salt]).delete('=')}",
36
- "Crypto-Key" => "dh=#{Base64.urlsafe_encode64(payload[:server_public_key_bn]).delete('=')}",
37
- "Ttl" => "2419200"
38
- }
39
- header["Authorization"] = "key=#{api_key}" unless api_key.empty?
40
- req = Net::HTTP::Post.new(uri.request_uri, header)
41
- req.body = payload[:ciphertext]
42
- res = http.request(req)
43
- return ("201" == res.code) ? true : false
44
- rescue
45
- return false
46
- end
39
+ def build_payload(message, p256dh, auth)
40
+ return {} if message.nil? || message.empty?
41
+
42
+ Webpush::Encryption.encrypt(message, p256dh, auth)
47
43
  end
48
44
  end
49
45
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webpush
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - zaru@sakuraba
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-05-12 00:00:00.000000000 Z
11
+ date: 2016-05-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hkdf
@@ -123,9 +123,11 @@ files:
123
123
  - Rakefile
124
124
  - bin/console
125
125
  - bin/rake
126
+ - bin/rspec
126
127
  - bin/setup
127
128
  - lib/webpush.rb
128
129
  - lib/webpush/encryption.rb
130
+ - lib/webpush/request.rb
129
131
  - lib/webpush/version.rb
130
132
  - webpush.gemspec
131
133
  homepage: https://github.com/zaru/webpush