pushpad 1.0.0 → 1.2.0

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
  SHA256:
3
- metadata.gz: 48d7160263b210c7897b4171bb69b21916e155e3e3c5c252a23ab99f0d64252d
4
- data.tar.gz: e06bc7a3e1d946403e45cc4ed57672d1fd97e4076d9d0538bf42ba7b13a5aaf1
3
+ metadata.gz: 6ca523c6dd48111c5a369889092c80690401158c3f0a14142eb3aee236848062
4
+ data.tar.gz: 637c8461863686d6c3c1f9f666a7df74b2053aed5017318f360054f908b83fbe
5
5
  SHA512:
6
- metadata.gz: 33bc016ccd42f4ea8cced6e3b0a98e91c22c196ff1481b504f64ae231ec0ff0d67c4e47bd5fb5fd0ab42c544d3d97e559893ed85d71cce7057e355a5d138863e
7
- data.tar.gz: 927b49b786b2ed3114d6a1c8b88dddcea72a21c6688a4bc9adc51e6206a5a0d6229134fdaace40c7687e957f0eba3a7045edb1e512ccfac4436568704bcdf803
6
+ metadata.gz: 7aaed7d9218f698803bf1adda9ba1459365b49eaba897903b1cab109aeb9bb825ab40b25119a52cc620d515dd7cce36ad8509d34baacb7b74d58028c26327207
7
+ data.tar.gz: 0e89ea2619f3c6f39c4a073c95d56faa451e672b86ad3d898e681b53c7ecef59af7e2b7b5fd22b3be957ab02cb7fcc98b6c7e4a84b5a328e853f6c5abc8905ac
@@ -10,7 +10,7 @@ jobs:
10
10
  strategy:
11
11
  fail-fast: false
12
12
  matrix:
13
- ruby-version: ['2.7', '3.0', '3.1']
13
+ ruby-version: ['3.0', '3.1', '3.2', '3.3']
14
14
  steps:
15
15
  - uses: actions/checkout@v3
16
16
  - name: Set up Ruby
data/README.md CHANGED
@@ -3,13 +3,9 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/pushpad.svg)](https://badge.fury.io/rb/pushpad)
4
4
  ![Build Status](https://github.com/pushpad/pushpad-ruby/workflows/CI/badge.svg)
5
5
 
6
- [Pushpad](https://pushpad.xyz) is a service for sending push notifications from your web app. It supports the **Push API** (Chrome, Firefox, Opera, Edge) and **APNs** (Safari desktop). iOS support is coming in 2023.
6
+ [Pushpad](https://pushpad.xyz) is a service for sending push notifications from websites and web apps. It uses the **Push API**, which is a standard supported by all major browsers (Chrome, Firefox, Opera, Edge, Safari).
7
7
 
8
- Features:
9
-
10
- - notifications are delivered even when the user is not on your website
11
- - users don't need to install any app or plugin
12
- - you can target specific users or send bulk notifications
8
+ The notifications are delivered in real time even when the users are not on your website and you can target specific users or send bulk notifications.
13
9
 
14
10
  ## Installation
15
11
 
@@ -55,17 +51,40 @@ Pushpad.signature_for current_user.id
55
51
 
56
52
  ```ruby
57
53
  notification = Pushpad::Notification.new({
54
+ # required, the main content of the notification
58
55
  body: "Hello world!",
59
- title: "Website Name", # optional, defaults to your project name
60
- target_url: "https://example.com", # optional, defaults to your project website
61
- icon_url: "https://example.com/assets/icon.png", # optional, defaults to the project icon
62
- badge_url: "https://example.com/assets/badge.png", # optional, defaults to the project badge
63
- image_url: "https://example.com/assets/image.png", # optional, an image to display in the notification content
64
- ttl: 604800, # optional, drop the notification after this number of seconds if a device is offline
65
- require_interaction: true, # optional, prevent Chrome on desktop from automatically closing the notification after a few seconds
66
- silent: false, # optional, enable this option if you want a mute notification without any sound
67
- urgent: false, # optional, enable this option only for time-sensitive alerts (e.g. incoming phone call)
68
- custom_data: "123", # optional, a string that is passed as an argument to action button callbacks
56
+
57
+ # optional, the title of the notification (defaults to your project name)
58
+ title: "Website Name",
59
+
60
+ # optional, open this link on notification click (defaults to your project website)
61
+ target_url: "https://example.com",
62
+
63
+ # optional, the icon of the notification (defaults to the project icon)
64
+ icon_url: "https://example.com/assets/icon.png",
65
+
66
+ # optional, the small icon displayed in the status bar (defaults to the project badge)
67
+ badge_url: "https://example.com/assets/badge.png",
68
+
69
+ # optional, an image to display in the notification content
70
+ # see https://pushpad.xyz/docs/sending_images
71
+ image_url: "https://example.com/assets/image.png",
72
+
73
+ # optional, drop the notification after this number of seconds if a device is offline
74
+ ttl: 604800,
75
+
76
+ # optional, prevent Chrome on desktop from automatically closing the notification after a few seconds
77
+ require_interaction: true,
78
+
79
+ # optional, enable this option if you want a mute notification without any sound
80
+ silent: false,
81
+
82
+ # optional, enable this option only for time-sensitive alerts (e.g. incoming phone call)
83
+ urgent: false,
84
+
85
+ # optional, a string that is passed as an argument to action button callbacks
86
+ custom_data: "123",
87
+
69
88
  # optional, add some action buttons to the notification
70
89
  # see https://pushpad.xyz/docs/action_buttons
71
90
  actions: [
@@ -76,10 +95,14 @@ notification = Pushpad::Notification.new({
76
95
  action: "myActionName" # optional
77
96
  }
78
97
  ],
79
- starred: true, # optional, bookmark the notification in the Pushpad dashboard (e.g. to highlight manual notifications)
98
+
99
+ # optional, bookmark the notification in the Pushpad dashboard (e.g. to highlight manual notifications)
100
+ starred: true,
101
+
80
102
  # optional, use this option only if you need to create scheduled notifications (max 5 days)
81
103
  # see https://pushpad.xyz/docs/schedule_notifications
82
104
  send_at: Time.utc(2016, 7, 25, 10, 9),
105
+
83
106
  # optional, add the notification to custom categories for stats aggregation
84
107
  # see https://pushpad.xyz/docs/monitoring
85
108
  custom_metrics: ['examples', 'another_metric'] # up to 3 metrics per notification
@@ -100,7 +123,8 @@ notification.deliver_to users, tags: ['events']
100
123
  notification.broadcast tags: ['segment1', 'segment2']
101
124
 
102
125
  # you can use boolean expressions
103
- # they must be in the disjunctive normal form (without parenthesis)
126
+ # they can include parentheses and the operators !, &&, || (from highest to lowest precedence)
127
+ # https://pushpad.xyz/docs/tags
104
128
  notification.broadcast tags: ['zip_code:28865 && !optout:local_events || friend_of:Organizer123']
105
129
  notification.deliver_to users, tags: ['tag1 && tag2', 'tag3'] # equal to 'tag1 && tag2 || tag3'
106
130
 
@@ -177,6 +201,26 @@ to get the full list in multiple requests.
177
201
  notifications = Pushpad::Notification.find_all(project_id: 5, page: 2)
178
202
  ```
179
203
 
204
+ ## Scheduled notifications
205
+
206
+ You can create scheduled notifications that will be sent in the future:
207
+
208
+ ```ruby
209
+ notification = Pushpad::Notification.new({
210
+ body: "This notification will be sent after 60 seconds",
211
+ send_at: Time.now.utc + 60
212
+ })
213
+
214
+ notification.broadcast
215
+ ```
216
+
217
+ You can also cancel a scheduled notification:
218
+
219
+ ```ruby
220
+ notification = Pushpad::Notification.find(5)
221
+ notification.cancel
222
+ ```
223
+
180
224
  ## Getting subscription count
181
225
 
182
226
  You can retrieve the number of subscriptions for a given project,
@@ -193,6 +237,29 @@ Pushpad::Subscription.count(project_id: 5, uids: ['user1'], tags: 'sports && tra
193
237
  If `Pushpad.project_id` is defined, the `project_id` option can be
194
238
  omitted.
195
239
 
240
+ ## Getting push subscription data
241
+
242
+ You can retrieve the subscriptions for a given project,
243
+ optionally filtered by `tags` or `uids`:
244
+
245
+ ```ruby
246
+ Pushpad::Subscription.find_all(project_id: 5)
247
+ Pushpad::Subscription.find_all(project_id: 5, uids: ['user1'])
248
+ Pushpad::Subscription.find_all(project_id: 5, tags: ['sports'])
249
+ Pushpad::Subscription.find_all(project_id: 5, tags: 'sports && travel')
250
+ Pushpad::Subscription.find_all(project_id: 5, uids: ['user1'], tags: 'sports && travel')
251
+ ```
252
+
253
+ If `Pushpad.project_id` is defined, the `project_id` option can be
254
+ omitted.
255
+
256
+ The REST API paginates the result set. You can pass a `page` parameter
257
+ to get the full list in multiple requests.
258
+
259
+ ```ruby
260
+ subscriptions = Pushpad::Subscription.find_all(project_id: 5, page: 2)
261
+ ```
262
+
196
263
  ## License
197
264
 
198
265
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -8,6 +8,9 @@ module Pushpad
8
8
 
9
9
  class FindError < RuntimeError
10
10
  end
11
+
12
+ class CancelError < RuntimeError
13
+ end
11
14
 
12
15
  class ReadonlyError < RuntimeError
13
16
  end
@@ -87,6 +90,14 @@ module Pushpad
87
90
  end
88
91
  deliver req_body(uids, options[:tags]), options
89
92
  end
93
+
94
+ def cancel
95
+ response = Request.delete("https://pushpad.xyz/api/v1/notifications/#{id}/cancel")
96
+
97
+ unless response.code == "204"
98
+ raise CancelError, "Response #{response.code} #{response.message}: #{response.body}"
99
+ end
100
+ end
90
101
 
91
102
  private
92
103
 
@@ -18,6 +18,10 @@ module Pushpad
18
18
  request.body = body
19
19
  end
20
20
  end
21
+
22
+ def delete(endpoint, options = {})
23
+ perform(Net::HTTP::Delete, endpoint, options)
24
+ end
21
25
 
22
26
  private
23
27
 
@@ -2,51 +2,67 @@ module Pushpad
2
2
  class Subscription
3
3
  class CountError < RuntimeError
4
4
  end
5
-
6
- def self.count(options = {})
7
- CountQuery.new(options).perform
5
+
6
+ class FindError < RuntimeError
8
7
  end
8
+
9
+ attr_reader :id, :endpoint, :p256dh, :auth, :uid, :tags, :last_click_at, :created_at
9
10
 
10
- class CountQuery
11
- attr_reader :options
11
+ def initialize(options)
12
+ @id = options[:id]
13
+ @endpoint = options[:endpoint]
14
+ @p256dh = options[:p256dh]
15
+ @auth = options[:auth]
16
+ @uid = options[:uid]
17
+ @tags = options[:tags]
18
+ @last_click_at = options[:last_click_at] && Time.parse(options[:last_click_at])
19
+ @created_at = options[:created_at] && Time.parse(options[:created_at])
20
+ end
12
21
 
13
- def initialize(options)
14
- @options = options
15
- end
22
+ def self.count(options = {})
23
+ project_id = options[:project_id] || Pushpad.project_id
24
+ raise "You must set project_id" unless project_id
16
25
 
17
- def perform
18
- project_id = options[:project_id] || Pushpad.project_id
19
- raise "You must set project_id" unless project_id
26
+ endpoint = "https://pushpad.xyz/api/v1/projects/#{project_id}/subscriptions"
27
+ response = Request.head(endpoint, query_parameters: query_parameters(options))
20
28
 
21
- endpoint = "https://pushpad.xyz/api/v1/projects/#{project_id}/subscriptions"
22
- response = Request.head(endpoint, query_parameters: query_parameters)
29
+ unless response.code == "200"
30
+ raise CountError, "Response #{response.code} #{response.message}: #{response.body}"
31
+ end
23
32
 
24
- unless response.code == "200"
25
- raise CountError, "Response #{response.code} #{response.message}: #{response.body}"
26
- end
33
+ response["X-Total-Count"].to_i
34
+ end
35
+
36
+ def self.find_all(options = {})
37
+ project_id = options[:project_id] || Pushpad.project_id
38
+ raise "You must set project_id" unless project_id
27
39
 
28
- response["X-Total-Count"].to_i
29
- end
40
+ query_parameters_with_pagination = query_parameters(options)
41
+ query_parameters_with_pagination << ["page", options[:page]] if options.key?(:page)
30
42
 
31
- private
43
+ response = Request.get("https://pushpad.xyz/api/v1/projects/#{project_id}/subscriptions",
44
+ query_parameters: query_parameters_with_pagination)
32
45
 
33
- def query_parameters
34
- [uid_query_parameters, tag_query_parameters].flatten(1)
46
+ unless response.code == "200"
47
+ raise FindError, "Response #{response.code} #{response.message}: #{response.body}"
35
48
  end
36
49
 
37
- def uid_query_parameters
38
- options.fetch(:uids, []).map { |uid| ["uids[]", uid] }
50
+ JSON.parse(response.body, symbolize_names: true).map do |attributes|
51
+ new(attributes)
39
52
  end
53
+ end
40
54
 
41
- def tag_query_parameters
42
- tags = options.fetch(:tags, [])
55
+ private
43
56
 
44
- if tags.is_a?(String)
45
- [["tags", tags]]
46
- else
47
- tags.map { |tag| ["tags[]", tag] }
48
- end
49
- end
57
+ def self.query_parameters(options)
58
+ uids = options.fetch(:uids, [])
59
+ uids_query = uids.map { |uid| ["uids[]", uid] }
60
+
61
+ tags = options.fetch(:tags, [])
62
+ tags_query = tags.is_a?(String) ? [["tags", tags]] : tags.map { |tag| ["tags[]", tag] }
63
+
64
+ [uids_query, tags_query].flatten(1)
50
65
  end
66
+
51
67
  end
52
68
  end
data/pushpad.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "pushpad"
3
- spec.version = '1.0.0'
3
+ spec.version = '1.2.0'
4
4
  spec.authors = ["Pushpad"]
5
5
  spec.email = ["support@pushpad.xyz"]
6
6
  spec.summary = "Web push notifications for Chrome, Firefox, Opera, Edge and Safari using Pushpad."
@@ -28,7 +28,6 @@ module Pushpad
28
28
  end
29
29
 
30
30
  def stub_notification_post(project_id, params = {}, response_body = "{}")
31
-
32
31
  stub_request(:post, "https://pushpad.xyz/api/v1/projects/#{project_id}/notifications").
33
32
  with(body: hash_including(params)).
34
33
  to_return(status: 201, body: response_body)
@@ -38,6 +37,16 @@ module Pushpad
38
37
  stub_request(:post, "https://pushpad.xyz/api/v1/projects/#{project_id}/notifications").
39
38
  to_return(status: 403)
40
39
  end
40
+
41
+ def stub_notification_delete(notification_id)
42
+ stub_request(:delete, "https://pushpad.xyz/api/v1/notifications/#{notification_id}/cancel").
43
+ to_return(status: 204)
44
+ end
45
+
46
+ def stub_failing_notification_delete(notification_id)
47
+ stub_request(:delete, "https://pushpad.xyz/api/v1/notifications/#{notification_id}/cancel").
48
+ to_return(status: 404)
49
+ end
41
50
 
42
51
  describe ".new" do
43
52
  it "allows delivering notifications even if an id attribute is supplied" do
@@ -409,5 +418,27 @@ module Pushpad
409
418
  end
410
419
  end
411
420
  end
421
+
422
+ describe "#cancel" do
423
+ it "cancels a scheduled notification" do
424
+ stub_notification_delete(5)
425
+
426
+ notification = Notification.new(id: 5)
427
+
428
+ res = notification.cancel
429
+ expect(res).to be_nil
430
+ end
431
+
432
+ it "fails with CancelError if response status code is not 204" do
433
+ stub_failing_notification_delete(5)
434
+
435
+ notification = Notification.new(id: 5)
436
+
437
+ expect {
438
+ notification.cancel
439
+ }.to raise_error(Notification::CancelError)
440
+ end
441
+ end
442
+
412
443
  end
413
444
  end
@@ -13,6 +13,17 @@ module Pushpad
13
13
  stub_request(:head, "https://pushpad.xyz/api/v1/projects/#{options[:project_id]}/subscriptions").
14
14
  to_return(status: 503)
15
15
  end
16
+
17
+ def stub_subscriptions_get(options)
18
+ stub_request(:get, "https://pushpad.xyz/api/v1/projects/#{options[:project_id]}/subscriptions").
19
+ with(query: hash_including(options.fetch(:query, {}))).
20
+ to_return(status: 200, body: options[:list].to_json)
21
+ end
22
+
23
+ def stub_failing_subscriptions_get(options)
24
+ stub_request(:get, "https://pushpad.xyz/api/v1/projects/#{options[:project_id]}/subscriptions").
25
+ to_return(status: 403)
26
+ end
16
27
 
17
28
  describe ".count" do
18
29
  it "returns value from X-Total-Count header" do
@@ -81,5 +92,110 @@ module Pushpad
81
92
  }.to raise_error(Subscription::CountError)
82
93
  end
83
94
  end
95
+
96
+ describe ".find_all" do
97
+ it "returns subscriptions of project with attributes from json response" do
98
+ attributes = {
99
+ id: 1169,
100
+ endpoint: "https://example.com/push/f7Q1Eyf7EyfAb1",
101
+ p256dh: "BCQVDTlYWdl05lal3lG5SKr3VxTrEWpZErbkxWrzknHrIKFwihDoZpc_2sH6Sh08h-CacUYI-H8gW4jH-uMYZQ4=",
102
+ auth: "cdKMlhgVeSPzCXZ3V7FtgQ==",
103
+ uid: "exampleUid",
104
+ tags: ["exampleTag1", "exampleTag2"],
105
+ last_click_at: "2023-11-03T10:30:00.000Z",
106
+ created_at: "2016-09-06T10:47:05.494Z"
107
+ }
108
+ stub_subscriptions_get(project_id: 10, list: [attributes])
109
+
110
+ subscriptions = Subscription.find_all(project_id: 10)
111
+
112
+ attributes.delete(:last_click_at)
113
+ attributes.delete(:created_at)
114
+ expect(subscriptions[0]).to have_attributes(attributes)
115
+ expect(subscriptions[0].last_click_at.utc.to_s).to eq(Time.utc(2023, 11, 3, 10, 30, 0.0).to_s)
116
+ expect(subscriptions[0].created_at.utc.to_s).to eq(Time.utc(2016, 9, 6, 10, 47, 5.494).to_s)
117
+ end
118
+
119
+ it "falls back to global project id" do
120
+ attributes = { id: 5 }
121
+ stub_subscriptions_get(project_id: 10, list: [attributes])
122
+
123
+ Pushpad.project_id = 10
124
+ subscriptions = Subscription.find_all
125
+
126
+ expect(subscriptions[0]).to have_attributes(attributes)
127
+ end
128
+
129
+ it "fails with helpful error message when project_id is missing" do
130
+ Pushpad.project_id = nil
131
+
132
+ expect {
133
+ Subscription.find_all
134
+ }.to raise_error(/must set project_id/)
135
+ end
136
+
137
+ it "allows passing page parameter for pagination" do
138
+ attributes = { id: 5 }
139
+ stub_subscriptions_get(project_id: 10, list: [attributes], query: { page: "3" })
140
+
141
+ subscriptions = Subscription.find_all(project_id: 10, page: 3)
142
+
143
+ expect(subscriptions[0]).to have_attributes(attributes)
144
+ end
145
+
146
+ it "fails with FindError if response status code is not 200" do
147
+ stub_failing_subscriptions_get(project_id: 10)
148
+
149
+ expect {
150
+ Subscription.find_all(project_id: 10)
151
+ }.to raise_error(Subscription::FindError)
152
+ end
153
+
154
+ it "allows passing uids" do
155
+ attributes = { id: 5 }
156
+ request = stub_subscriptions_get(project_id: 5, list: [attributes], query: { uids: ["uid0", "uid1"] })
157
+
158
+ Subscription.find_all(project_id: 5, uids: ["uid0", "uid1"])
159
+
160
+ expect(request).to have_been_made.once
161
+ end
162
+
163
+ it "allows passing tags" do
164
+ attributes = { id: 5 }
165
+ request = stub_subscriptions_get(project_id: 5, list: [attributes], query: { tags: ["sports", "travel"] })
166
+
167
+ Subscription.find_all(project_id: 5, tags: ["sports", "travel"])
168
+
169
+ expect(request).to have_been_made.once
170
+ end
171
+
172
+ it "allows passing tags as boolean expression" do
173
+ attributes = { id: 5 }
174
+ request = stub_subscriptions_get(project_id: 5, list: [attributes], query: { tags: "sports || travel" })
175
+
176
+ Subscription.find_all(project_id: 5, tags: "sports || travel")
177
+
178
+ expect(request).to have_been_made.once
179
+ end
180
+
181
+ it "allows passing tags and uids" do
182
+ attributes = { id: 5 }
183
+ request = stub_subscriptions_get(project_id: 5, list: [attributes],
184
+ query: { tags: ["sports", "travel"], uids: ["uid0"] })
185
+
186
+ Subscription.find_all(project_id: 5, tags: ["sports", "travel"], uids: ["uid0"])
187
+
188
+ expect(request).to have_been_made.once
189
+ end
190
+
191
+ it "works properly when there are no results" do
192
+ stub_subscriptions_get(project_id: 5, list: [])
193
+
194
+ subscriptions = Subscription.find_all(project_id: 5)
195
+
196
+ expect(subscriptions).to eq([])
197
+ end
198
+ end
199
+
84
200
  end
85
201
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pushpad
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pushpad
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-30 00:00:00.000000000 Z
11
+ date: 2024-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- description:
41
+ description:
42
42
  email:
43
43
  - support@pushpad.xyz
44
44
  executables: []
@@ -66,7 +66,7 @@ homepage: https://pushpad.xyz
66
66
  licenses:
67
67
  - MIT
68
68
  metadata: {}
69
- post_install_message:
69
+ post_install_message:
70
70
  rdoc_options: []
71
71
  require_paths:
72
72
  - lib
@@ -81,8 +81,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  requirements: []
84
- rubygems_version: 3.3.26
85
- signing_key:
84
+ rubygems_version: 3.0.3.1
85
+ signing_key:
86
86
  specification_version: 4
87
87
  summary: Web push notifications for Chrome, Firefox, Opera, Edge and Safari using
88
88
  Pushpad.