Packages
phoenix_kit
1.7.165
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/utils/geolocation.ex
defmodule PhoenixKit.Utils.Geolocation do
@moduledoc """
IP Geolocation utilities for PhoenixKit.
Provides functionality to extract IP addresses from Phoenix LiveView sockets
and look up geographical location data using free IP geolocation APIs.
## Features
- Extract IP addresses from Phoenix LiveView sockets
- Primary API: IP-API.com (45 requests/minute)
- Fallback API: ipapi.co (1000 requests/day)
- Graceful error handling with fallback to IP-only tracking
- Privacy-first design (disabled by default)
## Usage
# Extract IP from socket
ip_address = PhoenixKit.Utils.Geolocation.extract_ip_from_socket(socket)
# Lookup location data
case PhoenixKit.Utils.Geolocation.lookup_location(ip_address) do
{:ok, location} ->
# Process location data
{:error, reason} ->
# Handle error, fall back to IP-only
end
"""
require Logger
alias PhoenixKit.Utils.IpAddress
@doc """
Extracts IP address from a Phoenix LiveView socket.
## Examples
iex> extract_ip_from_socket(socket)
"192.168.1.1"
iex> extract_ip_from_socket(socket_with_no_peer_data)
"unknown"
"""
def extract_ip_from_socket(socket) do
IpAddress.extract_from_socket(socket)
end
@doc """
Looks up geographical location data for an IP address.
Uses IP-API.com as primary service (45 requests/minute) with ipapi.co
as fallback (1000 requests/day).
## Examples
iex> lookup_location("8.8.8.8")
{:ok, %{
"country" => "United States",
"region" => "California",
"city" => "Mountain View"
}}
iex> lookup_location("invalid-ip")
{:error, "Invalid IP address"}
"""
def lookup_location(ip_address) when is_binary(ip_address) do
case String.trim(ip_address) do
"" -> {:error, "Empty IP address"}
"unknown" -> {:error, "Unknown IP address"}
"127.0.0.1" -> {:error, "Localhost IP address"}
"::1" -> {:error, "Localhost IPv6 address"}
valid_ip -> perform_lookup(valid_ip)
end
end
def lookup_location(_), do: {:error, "Invalid IP address format"}
# Private functions
defp perform_lookup(ip_address) do
case lookup_with_ip_api(ip_address) do
{:ok, location} ->
{:ok, location}
{:error, reason} ->
Logger.warning("IP-API.com lookup failed: #{reason}, trying fallback")
lookup_with_ipapi_co(ip_address)
end
end
defp lookup_with_ip_api(ip_address) do
url = "http://ip-api.com/json/#{ip_address}?fields=status,message,country,regionName,city"
case make_http_request(url) do
{:ok, %{"status" => "success"} = data} ->
location = %{
"country" => data["country"],
"region" => data["regionName"],
"city" => data["city"]
}
{:ok, location}
{:ok, %{"status" => "fail", "message" => message}} ->
{:error, "IP-API.com error: #{message}"}
{:ok, _} ->
{:error, "Invalid response from IP-API.com"}
{:error, reason} ->
{:error, "HTTP request failed: #{reason}"}
end
end
defp lookup_with_ipapi_co(ip_address) do
url = "https://ipapi.co/#{ip_address}/json/"
case make_http_request(url) do
{:ok, %{"error" => true, "reason" => reason}} ->
{:error, "ipapi.co error: #{reason}"}
{:ok, data} when is_map(data) ->
location = %{
"country" => data["country_name"],
"region" => data["region"],
"city" => data["city"]
}
{:ok, location}
{:error, reason} ->
{:error, "HTTP request failed: #{reason}"}
end
end
# Uses Req (which rides its own auto-started Finch pool) rather than calling
# Finch directly against a `PhoenixKit.Finch` pool that nothing starts. Both
# APIs return JSON, so Req decodes the body to a map for us. `retry: false`
# keeps this single-shot — geolocation is best-effort on the registration path,
# so Req's default transient retries would only add latency before we fall back
# to IP-only tracking.
defp make_http_request(url) do
case Req.get(url, receive_timeout: 5_000, retry: false) do
{:ok, %{status: 200, body: body}} when is_map(body) ->
{:ok, body}
{:ok, %{status: 200}} ->
{:error, "Invalid JSON response"}
{:ok, %{status: status}} ->
{:error, "HTTP #{status}"}
{:error, reason} ->
{:error, Exception.message(reason)}
end
rescue
error ->
{:error, "Request exception: #{Exception.message(error)}"}
end
end