defmodule Mixpanel.Client do use GenServer @moduledoc """ Mixpanel API Client GenServer. """ require Logger alias Mixpanel.HTTP @type project_token :: String.t() @type active :: boolean @type base_url :: String.t() @type option :: {:project_token, project_token} | {:active, active} | {:base_url, base_url} @type init_args :: [option | GenServer.option(), ...] @type state :: %{ required(:project_token) => project_token, required(:active) => active, required(:base_url) => base_url } @type event :: String.t() | map @type properties :: map @type alias_id :: String.t() @type distinct_id :: String.t() @base_url "https://api.mixpanel.com" @track_endpoint "/track" @engage_endpoint "/engage" @alias_endpoint "/track#identity-create-alias" @spec start_link(init_args) :: :ignore | {:error, any} | {:ok, pid} def start_link(init_args) do {opts, gen_server_opts} = Keyword.split(init_args, [:project_token, :active, :base_url]) GenServer.start_link(__MODULE__, opts, gen_server_opts) end @spec child_spec(init_args) :: %{ id: __MODULE__, start: {__MODULE__, :start_link, [init_args, ...]} } def child_spec(init_args) do init_args = init_args |> Keyword.put_new(:name, __MODULE__) |> Keyword.put_new(:base_url, @base_url) |> Keyword.put_new(:active, true) %{ id: __MODULE__, start: {__MODULE__, :start_link, [init_args]} } end @doc """ Tracks a event See `Mixpanel.track/3` """ @spec track(event, properties) :: :ok def track(event, properties) do GenServer.cast(__MODULE__, {:track, event, properties}) end @doc """ Updates a user profile. See `Mixpanel.engage/4`. """ @spec engage(event | [event]) :: :ok def engage(event) do GenServer.cast(__MODULE__, {:engage, event}) end @doc """ Creates an alias for a user profile. See `Mixpanel.create_alias/2`. """ @spec create_alias(alias_id, distinct_id) :: :ok def create_alias(alias, distinct_id) do GenServer.cast(__MODULE__, {:create_alias, alias, distinct_id}) end @impl GenServer @spec init([option, ...]) :: {:ok, state} def init(opts) do project_token = Keyword.fetch!(opts, :project_token) active = Keyword.fetch!(opts, :active) base_url = Keyword.fetch!(opts, :base_url) {:ok, %{ project_token: project_token, active: active, base_url: base_url }} end @spec handle_cast( {:track, event, properties} | {:engage, event} | {:create_alias, alias_id, distinct_id}, state ) :: {:noreply, state} @impl GenServer def handle_cast( {:track, event, properties}, %{project_token: project_token, active: true} = state ) do data = %{event: event, properties: Map.put(properties, :token, project_token)} |> Jason.encode!() |> :base64.encode() case HTTP.get(state.base_url <> @track_endpoint, [], params: [data: data]) do {:ok, _, _, _} -> :ok _ -> Logger.warning(%{message: "Problem tracking event", event: event, properties: properties}) end {:noreply, state} end @impl GenServer def handle_cast({:engage, event}, %{project_token: project_token, active: true} = state) do data = event |> put_token(project_token) |> Jason.encode!() |> :base64.encode() case HTTP.get(state.base_url <> @engage_endpoint, [], params: [data: data]) do {:ok, _, _, _} -> :ok _ -> Logger.warning(%{message: "Problem tracking profile update", event: event}) end {:noreply, state} end @impl GenServer def handle_cast( {:create_alias, alias, distinct_id}, %{project_token: project_token, active: true} = state ) do data = %{ event: "$create_alias", properties: %{ token: project_token, alias: alias, distinct_id: distinct_id } } |> Jason.encode!() |> :base64.encode() case HTTP.post( state.base_url <> @alias_endpoint, "data=#{data}", [ {"Content-Type", "application/x-www-form-urlencoded"} ] ) do {:ok, _, _, _} -> :ok :ignore -> Logger.warning(%{ message: "Problem creating profile alias", alias: alias, distinct_id: distinct_id }) end {:noreply, state} end # No events submitted when env configuration is set to false. def handle_cast(_request, %{active: false} = state) do {:noreply, state} end defp put_token(events, project_token) when is_list(events), do: Enum.map(events, &put_token(&1, project_token)) defp put_token(event, project_token), do: Map.put(event, :"$token", project_token) end