instagram_connect 0.3.0 → 0.3.1

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: bfea76e8f86d1da429ba916fdf03124bb3eb0a4f0cc8d50bdeb5db838282026a
4
- data.tar.gz: b55274a8583dcad12bedca7025517fbfcea36865479f28c520c7c103e8e4d49a
3
+ metadata.gz: 1d1a0dd271f5e3f830351ddd25d62794b512c7786f125b6aa2f5ea8167db1423
4
+ data.tar.gz: 0e211865a1e788987b1fdb64f739bb2cde4c08ef57821b6b7602e23b22689cf3
5
5
  SHA512:
6
- metadata.gz: b9d2f5d664868d9444883e603684bf22e65880d8e75ef65d6014c14a7a6065cdf5d199b1d77c6c3c2f3178c7c804ebc921cdf08966d6c68b2ee18686a1c3a3f4
7
- data.tar.gz: d21363ec67aa080df5ff76747cf6f97db0b81963fe66716e7df44e44456dab3cf5b528e502f43c441a967a8d029459d2bb1dcef343b215979524bcc9c068bbbe
6
+ metadata.gz: 8df4d4149716e96900c767391f73c820b6777f0962c0f7e1d3c4e2522984f7af27da083ce75ada8092bd188dd9897053767e6c1fb2e7654291f6bff7a0c0ce66
7
+ data.tar.gz: 97a67eb17002fd5611fb76f53fc6e5a852ed8d8a62875f07a367258fe16758fa16556194b82fa61eea270ca131649df78027821618f3aadfb6c9fa92e5f0d269
data/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.1] - 2026-07-26
10
+
11
+ ### Fixed
12
+
13
+ - **Connecting an account failed when the Page came from the Login-for-Business asset picker.**
14
+ `/me/accounts` lists only the Pages a token may *enumerate*. A Page granted through the picker is
15
+ absent from that listing while reading perfectly well by id, so the list came back empty and the
16
+ connect was refused — on an account that was correctly linked and fully granted. Identity
17
+ resolution now falls back to the assets the token actually carries (`/debug_token` granular
18
+ scopes, which needs no permission beyond the app's own credentials) and reads those Pages by id.
19
+ - **A failed Graph call was reported as a missing account.** `resolve_identity` never checked
20
+ `Result#success?`, so an expired token or an HTTP 400 produced the same message as an empty list.
21
+ API failures now raise `ApiError` carrying Meta's own message.
22
+ - **The error message asserted a cause it had not verified.** It claimed no Instagram account was
23
+ linked to a Page, which was false in exactly the case that triggered it. It now states what was
24
+ actually checked.
25
+ - **`AccountReadinessJob` had the same flaw**, hunting for a known Page inside `/me/accounts`
26
+ instead of reading it by id — so the Page-token swap and the webhook subscription failed for the
27
+ same accounts, silently.
28
+
29
+
9
30
  ## [0.3.0] - 2026-07-26
10
31
 
11
32
  Conversations and engagement. The gem parsed 2 of the 15 webhook fields Meta can
@@ -87,8 +87,10 @@ module InstagramConnect
87
87
  return
88
88
  end
89
89
 
90
- pages = graph_get(account, "/me/accounts", fields: "id,access_token")
91
- page = Array(pages["data"]).find { |p| p["id"].to_s == account.page_id.to_s }
90
+ # Read the Page directly rather than hunting for it in /me/accounts: we
91
+ # already know its id, and the listing omits Pages granted through the
92
+ # Login-for-Business asset picker even though they read fine by id.
93
+ page = graph_get(account, "/#{account.page_id}", fields: "id,access_token")
92
94
 
93
95
  if page.nil? || page["access_token"].blank?
94
96
  account.update!(needs_reconnect: true,
@@ -157,6 +157,26 @@ module InstagramConnect
157
157
  get("/me/accounts", { fields: "id,name,access_token,instagram_business_account" })
158
158
  end
159
159
 
160
+ # One Page by id. /me/accounts answers for the Pages a token may *enumerate*;
161
+ # this answers for a Page it may *read*. Those are not the same set — a Page
162
+ # granted through the Login-for-Business asset picker is readable here while
163
+ # absent from the listing.
164
+ def page(page_id)
165
+ get("/#{page_id}", { fields: "id,name,access_token,instagram_business_account" })
166
+ end
167
+
168
+ # The asset ids this token actually carries, from Meta's own record of the
169
+ # consent. Needs no permission beyond the app's own credentials.
170
+ def granted_asset_ids
171
+ result = get("/debug_token", { input_token: access_token,
172
+ access_token: "#{config.app_id}|#{config.app_secret}" })
173
+ return [] unless result.success?
174
+
175
+ Array(result.data.dig("data", "granular_scopes"))
176
+ .flat_map { |scope| Array(scope["target_ids"]) }
177
+ .uniq
178
+ end
179
+
160
180
  # Walks a Graph collection to the end, following Meta's own cursors.
161
181
  #
162
182
  # Offsets are not safe here: a collection can shift between calls, so an
@@ -35,11 +35,34 @@ module InstagramConnect
35
35
  def resolve_identity(data)
36
36
  return { ig_user_id: data[:ig_user_id], page_id: data[:page_id] } if data[:ig_user_id]
37
37
 
38
- result = Client.new(access_token: data[:access_token], config: @config).list_pages
39
- page = Array(result.data["data"]).find { |p| p["instagram_business_account"] }
40
- raise ConfigurationError, "no Instagram business account is linked to a Page" unless page
38
+ client = Client.new(access_token: data[:access_token], config: @config)
39
+ page = candidate_pages(client).find { |p| p["instagram_business_account"] }
40
+ unless page
41
+ raise ConfigurationError,
42
+ "connected, but no Page with a linked Instagram business account was found — " \
43
+ "checked the Pages this token can list and the assets it was granted"
44
+ end
41
45
 
42
- { ig_user_id: page.dig("instagram_business_account", "id"), page_id: page["id"] }
46
+ { ig_user_id: page.dig("instagram_business_account", "id").to_s, page_id: page["id"].to_s }
47
+ end
48
+
49
+ # /me/accounts lists only the Pages a token may enumerate, and a Page granted
50
+ # through the Login-for-Business asset picker is not one of them — the list
51
+ # comes back empty while the Page itself reads fine. So when the listing has
52
+ # nothing usable, ask the token which assets it carries and read those.
53
+ def candidate_pages(client)
54
+ listed = client.list_pages
55
+ unless listed.success?
56
+ raise ApiError, "could not read Pages from Instagram: #{listed.error_message}"
57
+ end
58
+
59
+ pages = Array(listed.data["data"])
60
+ return pages if pages.any? { |p| p["instagram_business_account"] }
61
+
62
+ client.granted_asset_ids.filter_map do |id|
63
+ result = client.page(id)
64
+ result.data if result.success?
65
+ end
43
66
  end
44
67
  end
45
68
  end
@@ -1,3 +1,3 @@
1
1
  module InstagramConnect
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: instagram_connect
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kshitiz Sinha