pusher 1.1.0 → 1.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6daeeb10cfc7c29540cc4640cda4379f08c97a0a
4
- data.tar.gz: d1387f40e14a45e68c5b7c2ba457d68d0e6f04e8
3
+ metadata.gz: 32ce70117350a8d8229d42a09cfa152b95cbfbbd
4
+ data.tar.gz: 19e237323cf2d8d311de41eb2b72097f96d7f101
5
5
  SHA512:
6
- metadata.gz: 68870356440d4b596c15ad6a51d65769d89cb036e782cffaedccc9f47d4d4707020c4e02671919ccd3c519019da9bb02505a6bfa9739e7e96fe358e38adfef4c
7
- data.tar.gz: 3eeaa5528655dd1fc071e93adff493a39fc21843aea95f38b9076f8f9aebbb025f5b68afcd06f82bea733f985fa29f5e07611305051d5cc7bc4b8b04b95471af
6
+ metadata.gz: 00bbae6c4dd13a9672d53e7fe102990bb2afce06acfeaf0ad3b670d02a38d099c7a111e8ed805975ee9b75bced43caa202769e1a75a8669a6831e472093b572e
7
+ data.tar.gz: e1437819fb5d29c6e8036ac659496553cb93cf1ed2b1bb5cf3ebec16f15b869ee00fb9ee5c0c48f9b2230574149dc847c40d87d78f39fa6f147588198089e001
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ 1.2.0.rc1 / 2016-07-18
2
+ ==================
3
+
4
+ * Add support for Native notifications
1
5
 
2
6
  1.1.0 / 2016-05-20
3
7
  ==================
data/README.md CHANGED
@@ -247,3 +247,67 @@ else
247
247
  render text: 'invalid', status: 401
248
248
  end
249
249
  ```
250
+
251
+ ## Push Notifications (BETA)
252
+
253
+ Pusher now allows sending native notifications to iOS and Android devices. Check out the [documentation](https://pusher.com/docs/push_notifications) for information on how to set up push notifications on Android and iOS. There is no additional setup required to use it with this library. It works out of the box with the same Pusher instance. All you need are the same pusher credentials. To install the release:
254
+
255
+ ```
256
+ $ gem install pusher -v 1.2.0-rc1
257
+ ```
258
+
259
+ ### Sending native pushes
260
+
261
+ The native notifications API is hosted at `nativepush-cluster1.pusher.com` and only accepts https requests.
262
+
263
+ You can send pushes by using the `notify` method, either globally or on the instance. The method takes two parameters:
264
+
265
+ - `interests`: An Array of strings which represents the interests your devices are subscribed to. These are akin to channels in the DDN with less of an epehemeral nature. Note that currently, you can only send to _one_ interest.
266
+ - `data`: The content of the notification represented by a Hash. You must supply either the `gcm` or `apns` key. For a detailed list of the acceptable keys, take a look at the [iOS](https://pusher.com/docs/push_notifications/ios/server) and [Android](https://pusher.com/docs/push_notifications/android/server) docs.
267
+
268
+ Example:
269
+
270
+ ```ruby
271
+ data = {
272
+ apns: {
273
+ aps: {
274
+ alert: {
275
+ body: 'tada'
276
+ }
277
+ }
278
+ }
279
+ }
280
+
281
+ pusher.notify(["my-favourite-interest"], data)
282
+ ```
283
+
284
+ ### Errors
285
+
286
+ Push notification requests, once submitted to the service are executed asynchronously. To make reporting errors easier, you can supply a `webhook_url` field in the body of the request. This will be used by the service to send a webhook to the supplied URL if there are errors.
287
+
288
+ You may also supply a `webhook_level` field in the body, which can either be INFO or DEBUG. It defaults to INFO - where INFO only reports customer facing errors, while DEBUG reports all errors.
289
+
290
+ For example:
291
+
292
+ ```ruby
293
+ data = {
294
+ apns: {
295
+ aps: {
296
+ alert: {
297
+ body: "hello"
298
+ }
299
+ }
300
+ },
301
+ gcm: {
302
+ notification: {
303
+ title: "hello",
304
+ icon: "icon"
305
+ }
306
+ },
307
+ webhook_url: "http://yolo.com",
308
+ webhook_level: "INFO"
309
+ }
310
+ ```
311
+
312
+ **NOTE:** This is currently a BETA feature and there might be minor bugs and issues. Changes to the API will be kept to a minimum, but changes are expected. If you come across any bugs or issues, please do get in touch via [support](support@pusher.com) or create an issue here.
313
+
data/lib/pusher.rb CHANGED
@@ -28,7 +28,9 @@ module Pusher
28
28
  extend Forwardable
29
29
 
30
30
  def_delegators :default_client, :scheme, :host, :port, :app_id, :key, :secret, :http_proxy
31
+ def_delegators :default_client, :notification_host, :notification_scheme
31
32
  def_delegators :default_client, :scheme=, :host=, :port=, :app_id=, :key=, :secret=, :http_proxy=
33
+ def_delegators :default_client, :notification_host=, :notification_scheme=
32
34
 
33
35
  def_delegators :default_client, :authentication_token, :url
34
36
  def_delegators :default_client, :encrypted=, :url=, :cluster=
@@ -37,6 +39,7 @@ module Pusher
37
39
  def_delegators :default_client, :get, :get_async, :post, :post_async
38
40
  def_delegators :default_client, :channels, :channel_info, :channel_users, :trigger, :trigger_async
39
41
  def_delegators :default_client, :authenticate, :webhook, :channel, :[]
42
+ def_delegators :default_client, :notify
40
43
 
41
44
  attr_writer :logger
42
45
 
@@ -61,3 +64,4 @@ require 'pusher/channel'
61
64
  require 'pusher/request'
62
65
  require 'pusher/resource'
63
66
  require 'pusher/webhook'
67
+ require 'pusher/native_notification/client'
data/lib/pusher/client.rb CHANGED
@@ -2,7 +2,7 @@ require 'pusher-signature'
2
2
 
3
3
  module Pusher
4
4
  class Client
5
- attr_accessor :scheme, :host, :port, :app_id, :key, :secret
5
+ attr_accessor :scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme
6
6
  attr_reader :http_proxy, :proxy
7
7
  attr_writer :connect_timeout, :send_timeout, :receive_timeout,
8
8
  :keep_alive_timeout
@@ -32,9 +32,17 @@ module Pusher
32
32
  merged_options[:host] = "api.pusherapp.com"
33
33
  end
34
34
 
35
- @scheme, @host, @port, @app_id, @key, @secret = merged_options.values_at(
36
- :scheme, :host, :port, :app_id, :key, :secret
37
- )
35
+ # TODO: Change host name when finalized
36
+ merged_options[:notification_host] =
37
+ options.fetch(:notification_host, "nativepush-cluster1.pusher.com")
38
+
39
+ merged_options[:notification_scheme] =
40
+ options.fetch(:notification_scheme, "https")
41
+
42
+ @scheme, @host, @port, @app_id, @key, @secret, @notification_host, @notification_scheme =
43
+ merged_options.values_at(
44
+ :scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme
45
+ )
38
46
 
39
47
  @http_proxy = nil
40
48
  self.http_proxy = options[:http_proxy] if options[:http_proxy]
@@ -298,6 +306,25 @@ module Pusher
298
306
  post_async('/batch_events', trigger_batch_params(events.flatten))
299
307
  end
300
308
 
309
+ def notification_client
310
+ @notification_client ||=
311
+ NativeNotification::Client.new(@app_id, @notification_host, @notification_scheme, self)
312
+ end
313
+
314
+
315
+ # Send a push notification
316
+ #
317
+ # POST /apps/[app_id]/notifications
318
+ #
319
+ # @param interests [Array] An array of interests
320
+ # @param message [String] Message to send
321
+ # @param options [Hash] Additional platform specific options
322
+ #
323
+ # @return [Hash]
324
+ def notify(interests, data = {})
325
+ notification_client.notify(interests, data)
326
+ end
327
+
301
328
  # Generate the expected response for an authentication endpoint.
302
329
  # See http://pusher.com/docs/authenticating_users for details.
303
330
  #
@@ -0,0 +1,123 @@
1
+ module Pusher
2
+ module NativeNotification
3
+ class Client
4
+ attr_reader :app_id, :host
5
+
6
+ API_PREFIX = "customer_api"
7
+ API_VERSION = "v1"
8
+ GCM_TTL = 241920
9
+ RESTRICTED_GCM_PAYLOAD_KEYS = [:to, :registration_ids]
10
+ WEBHOOK_LEVELS = ["DEBUG", "INFO"]
11
+
12
+ def initialize(app_id, host, scheme, pusher_client)
13
+ @app_id = app_id
14
+ @host = host
15
+ @scheme = scheme
16
+ @pusher_client = pusher_client
17
+ end
18
+
19
+ # Send a notification via the native notifications API
20
+ def notify(interests, data = {})
21
+ Request.new(
22
+ @pusher_client,
23
+ :post,
24
+ url("/notifications"),
25
+ {},
26
+ payload(interests, data)
27
+ ).send_sync
28
+ end
29
+
30
+ private
31
+
32
+ # TODO: Actual links
33
+ #
34
+ # {
35
+ # interests: [Array of interests],
36
+ # apns: {
37
+ # See https://pusher.com/docs/push_notifications/ios/server
38
+ # },
39
+ # gcm: {
40
+ # See https://pusher.com/docs/push_notifications/android/server
41
+ # }
42
+ # }
43
+ #
44
+ # @raise [Pusher::Error] if the `apns` or `gcm` key does not exist
45
+ # @return [String]
46
+ def payload(interests, data)
47
+ interests = Array(interests).map(&:to_s)
48
+
49
+ raise Pusher::Error, "Too many interests provided" if interests.length > 1
50
+
51
+ data = deep_symbolize_keys!(data)
52
+ validate_payload(data)
53
+
54
+ data.merge!(interests: interests)
55
+
56
+ MultiJson.encode(data)
57
+ end
58
+
59
+ def url(path = nil)
60
+ URI.parse("#{@scheme}://#{@host}/#{API_PREFIX}/#{API_VERSION}/apps/#{@app_id}#{path}")
61
+ end
62
+
63
+ # Validate payload
64
+ # `time_to_live` -> value b/w 0 and 241920
65
+ # If the `notification` key is provided, ensure
66
+ # that there is an accompanying `title` and `icon`
67
+ # field
68
+ def validate_payload(payload)
69
+ unless (payload.has_key?(:apns) || payload.has_key?(:gcm))
70
+ raise Pusher::Error, "GCM or APNS data must be provided"
71
+ end
72
+
73
+ if (gcm_payload = payload[:gcm])
74
+ # Restricted keys
75
+ RESTRICTED_GCM_PAYLOAD_KEYS.each { |k| gcm_payload.delete(k) }
76
+ if (ttl = gcm_payload[:time_to_live])
77
+
78
+ if ttl.to_i < 0 || ttl.to_i > GCM_TTL
79
+ raise Pusher::Error, "Time to live must be between 0 and 241920 (4 weeks)"
80
+ end
81
+ end
82
+
83
+ # If the notification key is provided
84
+ # validate the `icon` and `title`keys
85
+ if (notification = gcm_payload[:notification])
86
+ notification_title, notification_icon = notification.values_at(:title, :icon)
87
+
88
+ if (!notification_title || notification_title.empty?)
89
+ raise Pusher::Error, "Notification title is a required field"
90
+ end
91
+
92
+ if (!notification_icon || notification_icon.empty?)
93
+ raise Pusher::Error, "Notification icon is a required field"
94
+ end
95
+ end
96
+ end
97
+
98
+ if (webhook_url = payload[:webhook_url])
99
+ raise Pusher::Error, "Webhook url is invalid" unless webhook_url =~ /\A#{URI::regexp(['http', 'https'])}\z/
100
+ end
101
+
102
+ if (webhook_level = payload[:webhook_level])
103
+ raise Pusher::Error, "Webhook level cannot be used without a webhook url" if !payload.has_key?(:webhook_url)
104
+
105
+ unless WEBHOOK_LEVELS.include?(webhook_level.upcase)
106
+ raise Pusher::Error, "Webhook level must either be INFO or DEBUG"
107
+ end
108
+ end
109
+ end
110
+
111
+ # Symbolize all keys in the hash recursively
112
+ def deep_symbolize_keys!(hash)
113
+ hash.keys.each do |k|
114
+ ks = k.respond_to?(:to_sym) ? k.to_sym : k
115
+ hash[ks] = hash.delete(k)
116
+ deep_symbolize_keys!(hash[ks]) if hash[ks].kind_of?(Hash)
117
+ end
118
+
119
+ hash
120
+ end
121
+ end
122
+ end
123
+ end
data/pusher.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |s|
3
3
  s.name = "pusher"
4
- s.version = "1.1.0"
4
+ s.version = "1.2.0.rc1"
5
5
  s.platform = Gem::Platform::RUBY
6
6
  s.authors = ["Pusher"]
7
7
  s.email = ["support@pusher.com"]
@@ -18,9 +18,9 @@ Gem::Specification.new do |s|
18
18
  s.add_development_dependency "rspec", "~> 3.0"
19
19
  s.add_development_dependency "webmock"
20
20
  s.add_development_dependency "em-http-request", "~> 1.1.0"
21
- s.add_development_dependency "rake"
22
- s.add_development_dependency "rack"
23
- s.add_development_dependency "json"
21
+ s.add_development_dependency "rake", "~> 10.4.2"
22
+ s.add_development_dependency "rack", "~> 1.6.4"
23
+ s.add_development_dependency "json", "~> 1.8.3"
24
24
 
25
25
  s.files = `git ls-files`.split("\n")
26
26
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
data/spec/client_spec.rb CHANGED
@@ -525,6 +525,206 @@ describe Pusher do
525
525
  end
526
526
  end
527
527
  end
528
+
529
+ describe "native notifications" do
530
+ before :each do
531
+ @client.app_id = "20"
532
+ @client.key = "testytest"
533
+ @client.secret = "mysupersecretkey"
534
+ end
535
+
536
+ it "should configure a native notification client using the pusher client object" do
537
+ expect(@client.notification_client).to_not be(nil)
538
+ end
539
+
540
+ it "should use the default host if not provided" do
541
+ expect(@client.notification_host).to eq("nativepush-cluster1.pusher.com")
542
+ end
543
+
544
+ it "should use a newly provided host" do
545
+ @client.notification_host = "test.com"
546
+ expect(@client.notification_host).to eq("test.com")
547
+ end
548
+
549
+ it "should set the native notification client host to the same one" do
550
+ expect(@client.notification_host).to eq(@client.notification_client.host)
551
+ end
552
+
553
+ it "should raise an error if the gcm or apns key isn't provided in the payload" do
554
+ expect { @client.notify(["test"], { foo: "bar" }) }.to raise_error(Pusher::Error)
555
+ end
556
+
557
+ it "should raise an error if more than one interest is provided" do
558
+ payload = {
559
+ gcm: {
560
+ notification: {
561
+ title: "Hello",
562
+ icon: "icon",
563
+ }
564
+ }
565
+ }
566
+
567
+ expect { @client.notify(["test1", "test2"], payload) }.to raise_error(Pusher::Error)
568
+ end
569
+
570
+ it "should raise an error if the notification hash is missing the title field" do
571
+ payload = {
572
+ gcm: {
573
+ notification: {
574
+ icon: "someicon"
575
+ }
576
+ }
577
+ }
578
+
579
+ expect{ @client.notify(["test"], payload) }.to raise_error(Pusher::Error)
580
+ end
581
+
582
+ it "should raise an error if the notification title is empty" do
583
+ payload = {
584
+ gcm: {
585
+ notification: {
586
+ title: "",
587
+ icon: "myicon"
588
+ }
589
+ }
590
+ }
591
+
592
+ expect { @client.notify(["test"], payload) }.to raise_error(Pusher::Error)
593
+ end
594
+
595
+ it "should raise an error if the notification hash is missing the icon field" do
596
+ payload = {
597
+ gcm: {
598
+ notification: {
599
+ title: "sometitle"
600
+ }
601
+ }
602
+ }
603
+
604
+ expect{ @client.notify(["test"], payload) }.to raise_error(Pusher::Error)
605
+ end
606
+
607
+ it "should raise an error if the notification icon is empty" do
608
+ payload = {
609
+ gcm: {
610
+ notification: {
611
+ title: "title",
612
+ icon: ""
613
+ }
614
+ }
615
+ }
616
+
617
+ expect { @client.notify(["test"], payload) }.to raise_error(Pusher::Error)
618
+ end
619
+
620
+ it "should raise an error if the ttl field is provided and has an illegal value" do
621
+ payload = {
622
+ gcm: {
623
+ time_to_live: 98091283,
624
+ notification: {
625
+ title: "title",
626
+ icon: "icon",
627
+ }
628
+ }
629
+ }
630
+
631
+ expect{ @client.notify(["test"], payload) }.to raise_error(Pusher::Error)
632
+ end
633
+
634
+ it "should send a request to the notifications endpoint" do
635
+ notification_host_regexp = %r{nativepush-cluster1.pusher.com}
636
+ payload = {
637
+ interests: ["test"],
638
+ gcm: {
639
+ notification: {
640
+ title: "Hello",
641
+ icon: "icon",
642
+ }
643
+ }
644
+ }
645
+
646
+ stub_request(
647
+ :post,
648
+ notification_host_regexp,
649
+ ).with(
650
+ body: MultiJson.encode(payload)
651
+ ).to_return({
652
+ :status => 200,
653
+ :body => MultiJson.encode({ :foo => "bar" })
654
+ })
655
+
656
+ @client.notify(["test"], payload)
657
+ end
658
+
659
+ it "should delete restricted gcm keys before sending a notification" do
660
+ notification_host_regexp = %r{nativepush-cluster1.pusher.com}
661
+ payload = {
662
+ interests: ["test"],
663
+ gcm: {
664
+ notification: {
665
+ title: "Hello",
666
+ icon: "icon",
667
+ }
668
+ }
669
+ }
670
+
671
+ stub_request(
672
+ :post,
673
+ notification_host_regexp,
674
+ ).with(
675
+ body: MultiJson.encode(payload)
676
+ ).to_return({
677
+ :status => 200,
678
+ :body => MultiJson.encode({ :foo => "bar" })
679
+ })
680
+
681
+ payload[:gcm].merge!(to: "blah", registration_ids: ["reg1", "reg2"])
682
+ @client.notify(["test"], payload)
683
+ end
684
+
685
+ it "should raise an error for an invalid webhook url field" do
686
+ payload = {
687
+ gcm: {
688
+ notification: {
689
+ title: "Hello",
690
+ icon: "icon"
691
+ }
692
+ },
693
+ webhook_url: "totallyinvalid"
694
+ }
695
+
696
+ expect { @client.notify(["test"], payload) }.to raise_error(Pusher::Error)
697
+ end
698
+
699
+ it "should raise an error if the webhook level is not supported" do
700
+ payload = {
701
+ gcm: {
702
+ notification: {
703
+ title: "Hello",
704
+ icon: "icon"
705
+ }
706
+ },
707
+ webhook_url: "http://test.com/wh",
708
+ webhook_level: "meh"
709
+ }
710
+
711
+ expect { @client.notify(["test"], payload) }.to raise_error(Pusher::Error)
712
+ end
713
+
714
+ it "should raise an error if the webhook level is used without the webhook url" do
715
+ payload = {
716
+ gcm: {
717
+ notification: {
718
+ title: "Hello",
719
+ icon: "icon"
720
+ }
721
+ },
722
+ webhook_level: "meh"
723
+ }
724
+
725
+ expect { @client.notify(["test"], payload) }.to raise_error(Pusher::Error)
726
+ end
727
+ end
528
728
  end
529
729
 
530
730
  describe 'configuring cluster' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pusher
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pusher
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-20 00:00:00.000000000 Z
11
+ date: 2016-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -98,44 +98,44 @@ dependencies:
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: 10.4.2
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: 10.4.2
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rack
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: 1.6.4
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: 1.6.4
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: json
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ">="
129
+ - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '0'
131
+ version: 1.8.3
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">="
136
+ - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '0'
138
+ version: 1.8.3
139
139
  description: Wrapper for pusher.com REST api
140
140
  email:
141
141
  - support@pusher.com
@@ -156,6 +156,7 @@ files:
156
156
  - lib/pusher.rb
157
157
  - lib/pusher/channel.rb
158
158
  - lib/pusher/client.rb
159
+ - lib/pusher/native_notification/client.rb
159
160
  - lib/pusher/request.rb
160
161
  - lib/pusher/resource.rb
161
162
  - lib/pusher/webhook.rb
@@ -179,9 +180,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
180
  version: '0'
180
181
  required_rubygems_version: !ruby/object:Gem::Requirement
181
182
  requirements:
182
- - - ">="
183
+ - - ">"
183
184
  - !ruby/object:Gem::Version
184
- version: '0'
185
+ version: 1.3.1
185
186
  requirements: []
186
187
  rubyforge_project:
187
188
  rubygems_version: 2.6.2