ExShopifyApp.AccessToken.Heartbeat (ex_shopify_app v1.3.0)
Periodically rotates token chains whose refresh token nears its 90-day expiry.
On-demand refreshing only fires when a shop's token is actually used, so a
dormant installation can silently cross the refresh-token cliff — after which
only the merchant relaunching the app restores API access. This process asks
the store for chains expiring inside :window (via
ExShopifyApp.AccessToken.Store.expiring_domains/2) and rotates them through
its lock-serialized refresh_token/2.
Registered locally under __MODULE__ by default, so one instance runs per
node. Pass {:global, __MODULE__} (or any other name) via :name to run a
single instance across the whole cluster instead — starting it on every node
then leaves one live process and the rest get
{:error, {:already_started, pid}}. Either way the work is idempotent: each
refresh re-checks under the per-shop row lock, so any concurrent tick collapses
into a single Shopify call per chain. Lifetime (non-expiring) rows carry no
refresh_token_expires_at and are never selected.
Add to your supervision tree:
{ExShopifyApp.AccessToken.Heartbeat, store: MyApp.ShopifyAccessTokens}Options
:store(required) — module implementingExShopifyApp.AccessToken.Store, including its optionalExShopifyApp.AccessToken.Store.expiring_domains/2callback:window— seconds before refresh-token expiry to rotate (default 7 days):interval— milliseconds between scans (default 6 hours):batch_limit— max chains refreshed per batch, closest expiry first (default 500). When a tick fills its batch the remaining chains are drained on an immediate follow-up tick rather than idling until the next:interval.:max_concurrency— chains rotated in parallel within a batch (default 10):name— process registration (default__MODULE__); pass{:global, __MODULE__}for a single cluster-wide instance, ornilto start an unregistered instance (used in tests)
Summary
Functions
Returns a specification to start this module under a supervisor.
Types
@type option() :: {:store, module()} | {:window, pos_integer()} | {:interval, pos_integer()} | {:batch_limit, pos_integer()} | {:max_concurrency, pos_integer()} | {:name, GenServer.name() | nil}
See the module documentation for the available options.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec start_link([option()]) :: GenServer.on_start()