Class: InstagramConnect::Account

Inherits:
ApplicationRecord show all
Defined in:
app/models/instagram_connect/account.rb

Overview

A connected Instagram professional account. Holds the (encrypted) access token and the identity the Graph client sends as. One row per connected account — the gem supports connecting several.

Constant Summary collapse

ENCRYPTED_ENVELOPE =

An Active Record Encryption envelope, as stored. With support_unencrypted_data on, a decrypt that FAILS (rotated or missing keys) hands the envelope back instead of raising — so an unreadable token reaches Meta as this JSON blob and comes back as the baffling "Cannot parse access token", with every call dead and nothing saying why.

/\A\{"p":/
TOKEN_UNREADABLE_MESSAGE =
"Stored access token cannot be decrypted with the current encryption " \
"keys — reconnect this Instagram account to store a fresh one.".freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.enable_token_encryption!Object

Called from the engine initializer (and specs) when token encryption is enabled. Kept as an explicit toggle so a host without Active Record Encryption configured can opt out via config.encrypt_tokens = false.



22
23
24
25
# File 'app/models/instagram_connect/account.rb', line 22

def self.enable_token_encryption!
  encrypts :access_token
  encrypts :page_access_token
end

Instance Method Details

#api_tokenObject

Every Instagram messaging call and the webhook subscription are Page-token operations. Falling back to the user token keeps a freshly connected account working until the readiness pass swaps in the Page one.



37
38
39
# File 'app/models/instagram_connect/account.rb', line 37

def api_token
  page_access_token.presence || access_token
end

#clientObject

The only place a Client should be built. Constructing one directly picks up the global auth path, which is wrong for any host with accounts on both paths — and wrong silently, which is worse.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'app/models/instagram_connect/account.rb', line 56

def client
  # Refuse to build a client around a token Meta can only reject, and
  # record WHY on the row so the host's health screen can say it out loud
  # instead of every screen quietly rendering empty.
  unless token_readable?
    update_columns(readiness_error: TOKEN_UNREADABLE_MESSAGE.truncate(255))
    raise InstagramConnect::TokenUnreadableError, TOKEN_UNREADABLE_MESSAGE
  end

  InstagramConnect::Client.new(
    access_token: api_token,
    ig_user_id: ig_user_id,
    config: InstagramConnect.configuration.for_auth_path(auth_path)
  )
end

#refresh_access_token!Object

Refresh via the account's auth strategy and persist the rotated token. Refreshes via the strategy this row was connected with, not the globally configured one.



83
84
85
86
87
# File 'app/models/instagram_connect/account.rb', line 83

def refresh_access_token!
  strategy = InstagramConnect::Auth.for(InstagramConnect.configuration.for_auth_path(auth_path))
  data = strategy.refresh_token(access_token: access_token)
  update!(access_token: data[:access_token], token_expires_at: data[:expires_at])
end

#subscribed_field_listObject



72
73
74
# File 'app/models/instagram_connect/account.rb', line 72

def subscribed_field_list
  subscribed_fields.to_s.split(",").map(&:strip).reject(&:empty?)
end

#token_expired?Boolean

Returns:

  • (Boolean)


76
77
78
# File 'app/models/instagram_connect/account.rb', line 76

def token_expired?
  token_expires_at.present? && token_expires_at <= Time.current
end

#token_readable?Boolean

False when the stored token cannot be decrypted with the keys this process has. Recovery is always the same: reconnect the account, which writes fresh tokens under the current keys.

Returns:

  • (Boolean)


44
45
46
47
# File 'app/models/instagram_connect/account.rb', line 44

def token_readable?
  token = api_token.to_s
  token.present? && !token.match?(ENCRYPTED_ENVELOPE)
end