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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b31a5377ff60ec4d4e5601e084d736e26bad0250ffb3c57f21e9763cbe32ce1f
4
- data.tar.gz: ecaf0a97ba89509bd0ce3eba1f33d37024d0a3cdac5ff2e6cfbe85ec9bdd0395
3
+ metadata.gz: b741101bdf4d7452a820bd7b988d9dff51f02cf4683d867fb332292ab0a4177e
4
+ data.tar.gz: 1516abffa7aea7972ebaf558c77bcd8999c397606bf66b9cf27f7519bc5198ea
5
5
  SHA512:
6
- metadata.gz: f98b8378045037ee79e53b5b5394beeb42b1ea92a0641cf0412e0598ec9bf92296568ebf8c46d625d260967e397e891110c204bd5fe6e69ae60e8d7e95cfccc2
7
- data.tar.gz: 9a92b7c19a5dada68228e6d2533b996352e74b104089f3cac3676db05e9d877b08eaedc207e268067710ca81b9ea6ce85f6952086f156c047ffe59b5852a5c8b
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
- ConnectedAccount = Struct.new(:id, :display, :granted_scopes, :calendar_permission_granted, keyword_init: true) do
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Mobiscroll
4
4
  module Connect
5
- VERSION = '1.2.0'
5
+ VERSION = '1.5.0'
6
6
  end
7
7
  end
@@ -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.2.0
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-08-27 00:00:00.000000000 Z
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