Tune.Spotify.Session.HTTP (tune v0.1.0)
This module implements a state machine mapped to a user session, wrapping interaction with the Spotify API.
If you're not familiar with the
gen_statem behaviour (which powers
GenStateMachine), it's beneficial to read
http://erlang.org/doc/design_principles/statem.html before proceeding
The state machine uses the
handle_event_function callback mode and has 3
┌─────────────────┐ │Not authenticated│ └─────────────────┘ │ │ ▼ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┌──────── Authenticate │─────────┐ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ │ │ Success Token Invalid │ expired token │ │ │ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │Authenticated│ │ Expired │ │ Stop │ └─────────────┘ └─────────────┘ └─────────────┘ ▲ │ ▲ │ Get new │ Success token │ │ ┌ ─ ─ ─ ─ │ │ └──── Refresh │◀───┘ │ └ ─ ─ ─ ─ │ │ │ │ Invalid │ └──────────refresh ─────────┘ token
When the process starts, it tries to authenticate against the Spotify API using the provided credentials. If successful, it enters the authenticated state, where all API functions can be executed correctly.
If authentication fails because the authentication token has expired, the process tries to get a new token using the refresh token supplied by the Spotify API. This process effectively extends the duration of the session.
Any error that indicates that credentials are invalid causes the process to stop. Any transient network error automatically triggers a delayed retry, which guarantees that eventually the state machine reaches the authenticated state.
Aside from acting as an API client for on-demand operations (e.g. search, play/pause, etc.), the state machine also regularly polls the Spotify API for current player status and connected devices. Both pieces of information are kept in the state machine data for fast read and corresponding events are broadcasted when they change.
Automatic data fetch is performed after successful authentication (via an
internal event) and then scheduled via a
state_timeout event. Once
handled, the scheduled event requeues itself via the same
state_timeout events complies with the general state machine: if
at any point the machine enters the expired state, any queued
event is automatically expired.
Automatic fetching will resume once the machine enters the authenticated state.
Multiple processes are able to subscribe to the events keyed by the session id.
Broadcast and subscribe are implemented via
Phoenix.PubSub, however the
state machine maintains its own set of monitored processes subscribed to the session
Subscription tracking is necessary to implementing automatic termination of a state machine after a period of inactivity. Without that, the state machine would indefinitely poll the Spotify API, even when no client is interested into the topic, until a crash error or a node reboot.
Every 30 seconds, the state machine fires a named
timeout event, checking if
there's any subscribed process. If not, it terminates. Subscribed processes
are monitored, so when they terminate, their exit is handled by the state machine,
which removes them from its data.
Usage of named
timeout events is necessary, as they're guaranteed to fire
irrespectively of state changes.