mobiscroll-connect 1.2.0 → 1.5.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 +4 -4
- data/README.md +23 -0
- data/lib/mobiscroll/connect/client.rb +2 -1
- data/lib/mobiscroll/connect/models.rb +54 -2
- data/lib/mobiscroll/connect/resources/webhooks.rb +42 -0
- data/lib/mobiscroll/connect/version.rb +1 -1
- data/lib/mobiscroll/connect.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b741101bdf4d7452a820bd7b988d9dff51f02cf4683d867fb332292ab0a4177e
|
|
4
|
+
data.tar.gz: 1516abffa7aea7972ebaf558c77bcd8999c397606bf66b9cf27f7519bc5198ea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5778815c08e0d3cda4217a0c0b559ef6ad9e840b7c83760409875a446c9bcfcd9d26ee96caa18c74ef4ba194e9d51e6974e1b32d3ce58daec68fbb2ab510b52c
|
|
7
|
+
data.tar.gz: '089dd3ea229d309050d63097ce83bc2b4f74a2c2513b1668a716154563b5a9d01e9e9a4ebe4ab384f1c9f9d83a3f7f804ac2dcbbc8681d831817f26a18412934'
|
data/README.md
CHANGED
|
@@ -170,6 +170,29 @@ client.events.delete(
|
|
|
170
170
|
)
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
+
## Webhooks
|
|
174
|
+
|
|
175
|
+
### Subscribe to calendar change notifications
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
result = client.webhooks.subscribe_webhook(
|
|
179
|
+
provider: Mobiscroll::Connect::Provider::GOOGLE,
|
|
180
|
+
calendar_id: 'primary'
|
|
181
|
+
)
|
|
182
|
+
puts result.channel_id
|
|
183
|
+
puts result.server_webhook_url
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Unsubscribe
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
client.webhooks.unsubscribe_webhook(
|
|
190
|
+
provider: 'google',
|
|
191
|
+
channel_id: result.channel_id,
|
|
192
|
+
resource_id: result.subscription.resource_id
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
173
196
|
## Error handling
|
|
174
197
|
|
|
175
198
|
All errors are subclasses of `Mobiscroll::Connect::Error`:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Mobiscroll
|
|
4
4
|
module Connect
|
|
5
5
|
class Client
|
|
6
|
-
attr_reader :auth, :calendars, :events
|
|
6
|
+
attr_reader :auth, :calendars, :events, :webhooks
|
|
7
7
|
|
|
8
8
|
def initialize(client_id:, client_secret:, redirect_uri:,
|
|
9
9
|
base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT,
|
|
@@ -20,6 +20,7 @@ module Mobiscroll
|
|
|
20
20
|
@auth = Resources::Auth.new(@config, @api_client)
|
|
21
21
|
@calendars = Resources::Calendars.new(@config, @api_client)
|
|
22
22
|
@events = Resources::Events.new(@config, @api_client)
|
|
23
|
+
@webhooks = Resources::Webhooks.new(@config, @api_client)
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def set_credentials(tokens)
|
|
@@ -57,7 +57,15 @@ module Mobiscroll
|
|
|
57
57
|
# no calendars can be read from them until the user reconnects and allows access. It
|
|
58
58
|
# is nil when the question does not apply (Apple and CalDav authenticate with a
|
|
59
59
|
# username and app password) or no scopes were recorded for the account.
|
|
60
|
-
|
|
60
|
+
# `sync_state` reports whether the stored credentials still work: 'active', or
|
|
61
|
+
# 'reauth_required' once the provider has rejected them. It answers a different question
|
|
62
|
+
# from `calendar_permission_granted`, which records what was agreed at consent time and
|
|
63
|
+
# never changes afterwards. An account with no observed failure is 'active'. Recovery is
|
|
64
|
+
# the same reconnect — it cannot be repaired server-side.
|
|
65
|
+
ConnectedAccount = Struct.new(
|
|
66
|
+
:id, :display, :granted_scopes, :calendar_permission_granted, :sync_state, :sync_state_updated_at,
|
|
67
|
+
keyword_init: true
|
|
68
|
+
) do
|
|
61
69
|
def self.from_h(hash)
|
|
62
70
|
return nil if hash.nil?
|
|
63
71
|
|
|
@@ -67,7 +75,9 @@ module Mobiscroll
|
|
|
67
75
|
id: hash['id'] || hash[:id],
|
|
68
76
|
display: hash['display'] || hash[:display],
|
|
69
77
|
granted_scopes: Array(hash['grantedScopes'] || hash[:grantedScopes]),
|
|
70
|
-
calendar_permission_granted: granted
|
|
78
|
+
calendar_permission_granted: granted,
|
|
79
|
+
sync_state: hash['syncState'] || hash[:syncState] || 'active',
|
|
80
|
+
sync_state_updated_at: hash['syncStateUpdatedAt'] || hash[:syncStateUpdatedAt]
|
|
71
81
|
)
|
|
72
82
|
end
|
|
73
83
|
end
|
|
@@ -200,5 +210,47 @@ module Mobiscroll
|
|
|
200
210
|
)
|
|
201
211
|
end
|
|
202
212
|
end
|
|
213
|
+
|
|
214
|
+
# The channel details returned by Webhooks#subscribe_webhook. `resource_id` is
|
|
215
|
+
# present for Google; `expiration` is an ISO date string, present for some providers.
|
|
216
|
+
WebhookSubscription = Struct.new(:channel_id, :resource_id, :expiration, keyword_init: true) do
|
|
217
|
+
def self.from_h(hash)
|
|
218
|
+
return nil if hash.nil?
|
|
219
|
+
|
|
220
|
+
new(
|
|
221
|
+
channel_id: hash['channelId'] || hash[:channelId],
|
|
222
|
+
resource_id: hash['resourceId'] || hash[:resourceId],
|
|
223
|
+
expiration: hash['expiration'] || hash[:expiration]
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Response from Webhooks#subscribe_webhook.
|
|
229
|
+
SubscribeWebhookResponse = Struct.new(
|
|
230
|
+
:success, :provider, :subscription, :server_webhook_url, :channel_id, keyword_init: true
|
|
231
|
+
) do
|
|
232
|
+
def self.from_h(hash)
|
|
233
|
+
return nil if hash.nil?
|
|
234
|
+
|
|
235
|
+
new(
|
|
236
|
+
success: hash['success'] || hash[:success],
|
|
237
|
+
provider: hash['provider'] || hash[:provider],
|
|
238
|
+
subscription: WebhookSubscription.from_h(hash['subscription'] || hash[:subscription]),
|
|
239
|
+
server_webhook_url: hash['serverWebhookUrl'] || hash[:serverWebhookUrl],
|
|
240
|
+
channel_id: hash['channelId'] || hash[:channelId]
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Response from Webhooks#unsubscribe_webhook. `success` is true whenever the local
|
|
246
|
+
# mapping was removed, even if the provider-side unsubscribe itself failed — `message`
|
|
247
|
+
# explains what happened in that case.
|
|
248
|
+
UnsubscribeWebhookResponse = Struct.new(:success, :message, keyword_init: true) do
|
|
249
|
+
def self.from_h(hash)
|
|
250
|
+
return nil if hash.nil?
|
|
251
|
+
|
|
252
|
+
new(success: hash['success'] || hash[:success], message: hash['message'] || hash[:message])
|
|
253
|
+
end
|
|
254
|
+
end
|
|
203
255
|
end
|
|
204
256
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Mobiscroll
|
|
6
|
+
module Connect
|
|
7
|
+
module Resources
|
|
8
|
+
class Webhooks
|
|
9
|
+
def initialize(_config, api_client)
|
|
10
|
+
@api_client = api_client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Subscribes to push notifications for changes on a calendar.
|
|
14
|
+
#
|
|
15
|
+
# `channel_id` is auto-generated server-side when omitted. `expiration` is a
|
|
16
|
+
# provider-specific timestamp (ms epoch) the caller can suggest.
|
|
17
|
+
def subscribe_webhook(provider:, calendar_id:, channel_id: nil, expiration: nil)
|
|
18
|
+
body = { 'provider' => provider, 'calendarId' => calendar_id }
|
|
19
|
+
body['channelId'] = channel_id if channel_id
|
|
20
|
+
body['expiration'] = expiration if expiration
|
|
21
|
+
|
|
22
|
+
parsed = @api_client.post('/subscribe-webhook', body: JSON.generate(body))
|
|
23
|
+
SubscribeWebhookResponse.from_h(parsed)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Unsubscribes a previously created webhook channel. `resource_id` is required by
|
|
27
|
+
# some providers (e.g. Google) to fully unsubscribe.
|
|
28
|
+
#
|
|
29
|
+
# The backend treats a 200 response as final even when the provider-side
|
|
30
|
+
# unsubscribe itself failed (e.g. an already-expired subscription) — the local
|
|
31
|
+
# mapping is removed either way, and `message` explains what happened.
|
|
32
|
+
def unsubscribe_webhook(provider:, channel_id:, resource_id: nil)
|
|
33
|
+
body = { 'provider' => provider, 'channelId' => channel_id }
|
|
34
|
+
body['resourceId'] = resource_id if resource_id
|
|
35
|
+
|
|
36
|
+
parsed = @api_client.post('/unsubscribe-webhook', body: JSON.generate(body))
|
|
37
|
+
UnsubscribeWebhookResponse.from_h(parsed)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/mobiscroll/connect.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative 'connect/api_client'
|
|
|
9
9
|
require_relative 'connect/resources/auth'
|
|
10
10
|
require_relative 'connect/resources/calendars'
|
|
11
11
|
require_relative 'connect/resources/events'
|
|
12
|
+
require_relative 'connect/resources/webhooks'
|
|
12
13
|
require_relative 'connect/client'
|
|
13
14
|
|
|
14
15
|
module Mobiscroll
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mobiscroll-connect
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mobiscroll
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -101,6 +101,7 @@ files:
|
|
|
101
101
|
- lib/mobiscroll/connect/resources/auth.rb
|
|
102
102
|
- lib/mobiscroll/connect/resources/calendars.rb
|
|
103
103
|
- lib/mobiscroll/connect/resources/events.rb
|
|
104
|
+
- lib/mobiscroll/connect/resources/webhooks.rb
|
|
104
105
|
- lib/mobiscroll/connect/version.rb
|
|
105
106
|
- mobiscroll-connect.gemspec
|
|
106
107
|
homepage: https://mobiscroll.com/connect
|