Compare commits

...

20 Commits

Author SHA1 Message Date
CI
0891706489 chore: release version v1.97.5 2026-03-26 01:02:39 +00:00
Dmitry Popov
7d720dcfb5 Merge branch 'main' of github.com:wanderer-industries/wanderer 2026-03-26 02:02:08 +01:00
Dmitry Popov
63b40b9c75 fix(core): Fixed character re-auth issues 2026-03-26 02:02:04 +01:00
CI
fc167fafaf chore: [skip ci] 2026-03-26 00:11:47 +00:00
CI
b9197880f0 chore: release version v1.97.4 2026-03-26 00:11:47 +00:00
Dmitry Popov
88f027facd Merge branch 'main' of github.com:wanderer-industries/wanderer 2026-03-26 01:11:01 +01:00
Dmitry Popov
d62ad709ab fix(core): Fixed character re-auth issues 2026-03-26 01:10:57 +01:00
CI
15aeb8eb85 chore: [skip ci] 2026-03-25 23:41:47 +00:00
CI
6970db438d chore: release version v1.97.3 2026-03-25 23:41:47 +00:00
Dmitry Popov
9ab7fcc46e fix(core): Fixed character re-auth issues 2026-03-26 00:41:07 +01:00
CI
931a8e629d chore: [skip ci] 2026-03-23 11:20:01 +00:00
CI
a06df968a2 chore: release version v1.97.2 2026-03-23 11:20:01 +00:00
Dmitry Popov
965df31da0 Merge branch 'fix-add-from-routes' 2026-03-23 12:19:25 +01:00
Dmitry Popov
171591f07d fix(core): Fixed tracking issues & adding systems to map from routes 2026-03-23 12:19:17 +01:00
CI
9a63700dfb chore: [skip ci] 2026-03-21 15:23:02 +00:00
CI
092fdb01e5 chore: release version v1.97.1 2026-03-21 15:23:02 +00:00
Dmitry Popov
0ae9b54e3f Merge pull request #599 from wanderer-industries/new-wormholes
fix: Update some wormholes data accordingly on patch notes info.
2026-03-21 16:22:28 +01:00
DanSylvest
d4928c0195 fix: Add new Pochven medium wormholes (I078, L687, O546). Change lifetime to 12H from 16H for X450, U372, R081, F216. 2026-03-20 10:25:37 +03:00
DanSylvest
f00deb1556 chore: Changed old API for adding system from routes to new. 2026-03-17 10:28:15 +03:00
CI
11b0ba4018 chore: [skip ci] 2026-03-14 22:59:13 +00:00
12 changed files with 272 additions and 67 deletions

View File

@@ -2,6 +2,51 @@
<!-- changelog -->
## [v1.97.5](https://github.com/wanderer-industries/wanderer/compare/v1.97.4...v1.97.5) (2026-03-26)
### Bug Fixes:
* core: Fixed character re-auth issues
## [v1.97.4](https://github.com/wanderer-industries/wanderer/compare/v1.97.3...v1.97.4) (2026-03-26)
### Bug Fixes:
* core: Fixed character re-auth issues
## [v1.97.3](https://github.com/wanderer-industries/wanderer/compare/v1.97.2...v1.97.3) (2026-03-25)
### Bug Fixes:
* core: Fixed character re-auth issues
## [v1.97.2](https://github.com/wanderer-industries/wanderer/compare/v1.97.1...v1.97.2) (2026-03-23)
### Bug Fixes:
* core: Fixed tracking issues & adding systems to map from routes
## [v1.97.1](https://github.com/wanderer-industries/wanderer/compare/v1.97.0...v1.97.1) (2026-03-21)
### Bug Fixes:
* Add new Pochven medium wormholes (I078, L687, O546). Change lifetime to 12H from 16H for X450, U372, R081, F216.
## [v1.97.0](https://github.com/wanderer-industries/wanderer/compare/v1.96.6...v1.97.0) (2026-03-14)

View File

@@ -49,9 +49,9 @@ export const useContextMenuSystemInfoHandlers = () => {
}
outCommand({
type: OutCommand.addSystem,
type: OutCommand.manualAddSystem,
data: {
system_id: solarSystemId,
solar_system_id: parseInt(solarSystemId),
},
});

View File

@@ -115,9 +115,11 @@ defmodule WandererApp.Character.TrackingUtils do
end)}
end
# Filter characters to only include those with actual tracking permission
# This prevents showing characters in the tracking dialog that will fail when toggled
defp filter_characters_with_tracking_permission(characters, %{id: map_id, owner_id: owner_id}) do
@doc """
Filters a list of characters to only include those with actual tracking permission on a map.
This prevents showing characters in the tracking dialog that will fail when toggled.
"""
def filter_characters_with_tracking_permission(characters, %{id: map_id, owner_id: owner_id}) do
# Load ACLs with members properly (same approach as get_map_characters)
acls = load_map_acls_with_members(map_id)

View File

@@ -897,20 +897,43 @@ defmodule WandererApp.Esi.ApiClient do
end
defp invalidate_character_tokens(character, character_id, expires_at, scopes) do
attrs = %{access_token: nil, refresh_token: nil, expires_at: expires_at, scopes: scopes}
with {:ok, _} <- WandererApp.Api.Character.update(character, attrs) do
WandererApp.Character.update_character(character_id, attrs)
# Skip invalidation if the character was recently re-authorized via SSO.
# This protects fresh tokens from being wiped by transient invalid_grant
# errors that can occur shortly after re-auth.
if WandererApp.Cache.lookup!("character:#{character_id}:reauth_grace", false) do
Logger.info(
"[ApiClient] Skipping token invalidation for #{character_id} - within re-auth grace period"
)
else
error ->
Logger.error("Failed to clear tokens for #{character_id}: #{inspect(error)}")
end
# Re-load from DB to avoid race with concurrent re-auth
case WandererApp.Api.Character.by_id(character_id) do
{:ok, current_character} ->
# Only invalidate if tokens haven't been refreshed since we started
if current_character.access_token == character.access_token do
attrs = %{access_token: nil, refresh_token: nil, expires_at: expires_at, scopes: scopes}
Phoenix.PubSub.broadcast(
WandererApp.PubSub,
"character:#{character_id}",
:character_token_invalid
)
with {:ok, _} <- WandererApp.Api.Character.update(current_character, attrs) do
WandererApp.Character.update_character(character_id, attrs)
else
error ->
Logger.error("Failed to clear tokens for #{character_id}: #{inspect(error)}")
end
Phoenix.PubSub.broadcast(
WandererApp.PubSub,
"character:#{character_id}",
:character_token_invalid
)
else
Logger.info(
"[ApiClient] Skipping token invalidation for #{character_id} - tokens were refreshed concurrently"
)
end
{:error, _} ->
Logger.error("Failed to load character #{character_id} for token invalidation")
end
end
:ok
end

View File

@@ -789,8 +789,7 @@ defmodule WandererApp.Map.Server.SystemsImpl do
defp do_add_system(
map_id,
%{
solar_system_id: solar_system_id,
coordinates: coordinates
solar_system_id: solar_system_id
} = system_info,
user_id,
character_id
@@ -803,19 +802,14 @@ defmodule WandererApp.Map.Server.SystemsImpl do
rtree_name = "rtree_#{map_id}"
%{"x" => x, "y" => y} =
coordinates
system_info
|> Map.get(:coordinates)
|> case do
%{"x" => x, "y" => y} ->
%{"x" => x, "y" => y}
_ ->
%{x: x, y: y} =
WandererApp.Map.PositionCalculator.get_new_system_position(
nil,
rtree_name,
map_opts
)
{:ok, %{x: x, y: y}} = calc_new_system_position(map_id, nil, rtree_name, map_opts)
%{"x" => x, "y" => y}
end

View File

@@ -46,14 +46,18 @@ defmodule WandererApp.Ueberauth.Strategy.Eve do
|> with_param(:hl, conn)
|> with_state_param(conn)
opts = oauth_client_options_from_conn(conn, with_wallet, is_admin?)
WandererApp.Cache.put(
"eve_auth_#{params[:state]}",
[with_wallet: with_wallet, is_admin?: is_admin?],
[
with_wallet: with_wallet,
is_admin?: is_admin?,
tracking_pool: Keyword.get(opts, :tracking_pool)
],
ttl: :timer.minutes(30)
)
opts = oauth_client_options_from_conn(conn, with_wallet, is_admin?)
redirect!(conn, WandererApp.Ueberauth.Strategy.Eve.OAuth.authorize_url!(params, opts))
false ->

View File

@@ -10,6 +10,12 @@ defmodule WandererAppWeb.AuthController do
def callback(%{assigns: %{ueberauth_auth: auth, current_user: user} = _assigns} = conn, _params) do
active_tracking_pool = WandererApp.Character.TrackingConfigUtils.get_active_pool!()
Logger.info(
"[AuthController] SSO callback SUCCESS for eve_id=#{auth.info.email}, " <>
"has_token=#{not is_nil(auth.credentials.token)}, " <>
"has_refresh=#{not is_nil(auth.credentials.refresh_token)}"
)
character_data = %{
eve_id: "#{auth.info.email}",
name: auth.info.name,
@@ -40,8 +46,25 @@ defmodule WandererAppWeb.AuthController do
character
|> WandererApp.Api.Character.update(character_update)
Logger.info(
"[AuthController] Character #{character.id} tokens updated in DB, " <>
"access_token_present=#{not is_nil(character.access_token)}"
)
WandererApp.Character.update_character(character.id, character_update)
# Clear the invalid_grant counter so stale failures don't cause
# premature token invalidation after a successful re-auth
WandererApp.Cache.delete("character:#{character.id}:invalid_grant_count")
# Set a grace period to protect fresh tokens from being wiped by
# in-flight or immediately-subsequent invalid_grant errors
WandererApp.Cache.put(
"character:#{character.id}:reauth_grace",
true,
ttl: :timer.minutes(5)
)
# Update corporation/alliance data from ESI to ensure access control is current
update_character_affiliation(character)
@@ -96,7 +119,16 @@ defmodule WandererAppWeb.AuthController do
end
def callback(conn, _params) do
# This runs when Ueberauth auth FAILED — tokens are NOT updated
ueberauth_failure = conn.assigns[:ueberauth_failure]
Logger.warning(
"[AuthController] SSO callback FAILED - no ueberauth_auth in assigns. " <>
"Failure: #{inspect(ueberauth_failure)}"
)
conn
|> put_flash(:error, "Authorization failed. Please try again.")
|> redirect(to: "/characters")
end

View File

@@ -22,6 +22,11 @@ defmodule WandererAppWeb.CharactersLive do
"character:#{character_id}:corporation"
)
Phoenix.PubSub.subscribe(
WandererApp.PubSub,
"character:#{character_id}"
)
:ok = WandererApp.Character.TrackerManager.start_tracking(character_id)
end)
@@ -83,12 +88,11 @@ defmodule WandererAppWeb.CharactersLive do
{:ok, _} = WandererApp.MapCharacterSettingsRepo.untrack(settings)
end)
{:ok, updated_character} =
socket.assigns.characters
|> Enum.find(&(&1.id == character_id))
|> WandererApp.Api.Character.mark_as_deleted()
# Load character from DB instead of using plain map from assigns
{:ok, character} = WandererApp.Api.Character.by_id(character_id)
{:ok, _updated_character} = WandererApp.Api.Character.mark_as_deleted(character)
WandererApp.Character.update_character(character_id, updated_character)
WandererApp.Character.update_character(character_id, %{deleted: true, user_id: nil})
{:ok, characters} =
WandererApp.Api.Character.active_by_user(%{user_id: socket.assigns.user_id})
@@ -148,6 +152,18 @@ defmodule WandererAppWeb.CharactersLive do
{:noreply, socket |> assign(characters: characters |> Enum.map(&map_ui_character/1))}
end
@impl true
def handle_info(
event,
socket
)
when event in [:character_token_invalid, :token_updated] do
{:ok, characters} =
WandererApp.Api.Character.active_by_user(%{user_id: socket.assigns.user_id})
{:noreply, socket |> assign(characters: characters |> Enum.map(&map_ui_character/1))}
end
@impl true
def handle_info(
_event,

View File

@@ -54,7 +54,7 @@ defmodule WandererAppWeb.CharactersTrackingLive do
selected_map_slug: map_slug
)
|> assign_async(:characters, fn ->
WandererApp.Maps.load_characters(selected_map, current_user.id)
load_trackable_characters(selected_map, current_user.id)
end)
end
@@ -100,37 +100,55 @@ defmodule WandererAppWeb.CharactersTrackingLive do
selected_map = socket.assigns.selected_map
%{result: characters} = socket.assigns.characters
case characters |> Enum.find(&(&1.id == character_id)) do
%{tracked: current_tracked, eve_id: eve_id} ->
# Use TrackingUtils.update_tracking to properly set/unset the tracking_start_time
# cache key, which is required for the character to appear in get_tracked_character_ids
case TrackingUtils.update_tracking(
selected_map.id,
eve_id,
current_user.id,
not current_tracked,
self(),
false
) do
{:ok, _tracking_data, _event} ->
:ok
result =
case characters |> Enum.find(&(&1.id == character_id)) do
%{tracked: current_tracked, eve_id: eve_id} ->
# Use TrackingUtils.update_tracking to properly set/unset the tracking_start_time
# cache key, which is required for the character to appear in get_tracked_character_ids
case TrackingUtils.update_tracking(
selected_map.id,
eve_id,
current_user.id,
not current_tracked,
self(),
false
) do
{:ok, _tracking_data, _event} ->
:ok
{:error, reason} ->
Logger.error(
"Failed to toggle tracking for character #{character_id} on map #{selected_map.id}: #{inspect(reason)}"
)
end
{:error, reason} ->
Logger.error(
"Failed to toggle tracking for character #{character_id} on map #{selected_map.id}: #{inspect(reason)}"
)
nil ->
Logger.warning(
"Character #{character_id} not found in available characters for map #{selected_map.id}"
)
end
{:error, reason}
end
nil ->
Logger.warning(
"Character #{character_id} not found in available characters for map #{selected_map.id}"
)
{:error, "Character not found"}
end
socket =
case result do
{:error, _reason} ->
put_flash(
socket,
:error,
"Failed to toggle tracking. Character may not have sufficient permissions on this map."
)
_ ->
socket
end
{:noreply,
socket
|> assign_async(:characters, fn ->
WandererApp.Maps.load_characters(selected_map, current_user.id)
load_trackable_characters(selected_map, current_user.id)
end)}
end
@@ -154,10 +172,21 @@ defmodule WandererAppWeb.CharactersTrackingLive do
{:noreply,
socket
|> assign_async(:characters, fn ->
WandererApp.Maps.load_characters(selected_map, current_user.id)
load_trackable_characters(selected_map, current_user.id)
end)}
end
@impl true
def handle_info(_event, socket), do: {:noreply, socket}
defp load_trackable_characters(map, user_id) do
case WandererApp.Maps.load_characters(map, user_id) do
{:ok, %{characters: characters}} ->
filtered = TrackingUtils.filter_characters_with_tracking_permission(characters, map)
{:ok, %{characters: filtered}}
error ->
error
end
end
end

View File

@@ -128,6 +128,33 @@ defmodule WandererAppWeb.MapSystemsEventHandler do
{:noreply, socket}
end
def handle_ui_event(
"manual_add_system",
%{"solar_system_id" => solar_system_id} = _event,
%{
assigns: %{
current_user: %{id: current_user_id},
has_tracked_characters?: true,
map_id: map_id,
main_character_id: main_character_id,
user_permissions: %{add_system: true}
}
} =
socket
)
when not is_nil(main_character_id) do
WandererApp.Map.Server.add_system(
map_id,
%{
solar_system_id: solar_system_id
},
current_user_id,
main_character_id
)
{:noreply, socket}
end
def handle_ui_event(
"manual_paste_systems_and_connections",
%{

View File

@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.97.0"
@version "1.97.5"
def project do
[

View File

@@ -291,7 +291,7 @@
"src": ["c2", "c3", "c4", "c5", "c6"],
"static": false,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"lifetime": "12",
"total_mass": 1000000000,
"name": "F216",
"respawn": ["wandering", "reverse"]
@@ -698,7 +698,7 @@
"src": ["pochven"],
"static": false,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"lifetime": "12",
"total_mass": 1000000000,
"name": "R081",
"respawn": ["wandering"]
@@ -830,7 +830,7 @@
"src": ["ns"],
"static": false,
"max_mass_per_jump": 375000000,
"lifetime": "16",
"lifetime": "12",
"total_mass": 1000000000,
"name": "U372",
"respawn": ["wandering", "reverse"]
@@ -929,7 +929,7 @@
"src": ["pochven"],
"static": false,
"max_mass_per_jump": 62000000,
"lifetime": "16",
"lifetime": "12",
"total_mass": 1000000000,
"name": "X450",
"respawn": ["wandering"]
@@ -1065,5 +1065,38 @@
"total_mass": "",
"name": "K162",
"respawn": []
},
{
"mass_regen": 0,
"dest": "pochven",
"src": ["pochven"],
"static": false,
"max_mass_per_jump": 62000000,
"lifetime": "4.5",
"total_mass": 100000000,
"name": "I078",
"respawn": ["wandering"]
},
{
"mass_regen": 0,
"dest": "pochven",
"src": ["pochven"],
"static": false,
"max_mass_per_jump": 62000000,
"lifetime": "4.5",
"total_mass": 100000000,
"name": "L687",
"respawn": ["wandering"]
},
{
"mass_regen": 0,
"dest": "pochven",
"src": ["pochven"],
"static": false,
"max_mass_per_jump": 62000000,
"lifetime": "4.5",
"total_mass": 100000000,
"name": "O546",
"respawn": ["wandering"]
}
]