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 implementing ExShopifyApp.AccessToken.Store, including its optional ExShopifyApp.AccessToken.Store.expiring_domains/2 callback
  • :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, or nil to start an unregistered instance (used in tests)

Summary

Types

See the module documentation for the available options.

Functions

Returns a specification to start this module under a supervisor.

Types

option()

@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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts)

@spec start_link([option()]) :: GenServer.on_start()