diff --git a/src/date_ffi.mjs b/src/date_ffi.mjs index 96c00c5..279e82c 100644 --- a/src/date_ffi.mjs +++ b/src/date_ffi.mjs @@ -17,6 +17,10 @@ export function to_iso8601(date) { return date.toISOString(); } +export function to_unix(date) { + return date.getTime(); +} + export function unix_now() { return Date.now(); } diff --git a/src/geo_t/config.gleam b/src/geo_t/config.gleam index 4efcadb..b8af9d8 100644 --- a/src/geo_t/config.gleam +++ b/src/geo_t/config.gleam @@ -12,6 +12,7 @@ pub type Config { debug: Bool, api_timeout: Int, api_auth_url: Uri, + api_auth_token_refresh_diff: Int, api_installations_url: Uri, api_device_url: Uri, api_device_status_url: Uri, @@ -38,6 +39,10 @@ pub fn load_config() -> Config { config_helpers.api_auth_url, "https://thermia-auth-api.azurewebsites.net/api/v1/Jwt/login", ), + api_auth_token_refresh_diff: get_env( + config_helpers.api_auth_token_refresh_diff, + 300_000, + ), api_installations_url: get_uri( config_helpers.api_installations_url, "https://online-genesis-serviceapi.azurewebsites.net/api/v1/installationsInfo", diff --git a/src/geo_t/helpers/config.gleam b/src/geo_t/helpers/config.gleam index 87e583f..432213a 100644 --- a/src/geo_t/helpers/config.gleam +++ b/src/geo_t/helpers/config.gleam @@ -16,6 +16,10 @@ pub fn api_auth_url() { config_url("api_auth_url") } +pub fn api_auth_token_refresh_diff() { + config("api_auth_token_refresh_diff", dynamic.int) +} + pub fn api_installations_url() { config_url("api_installations_url") } diff --git a/src/geo_t/helpers/date.gleam b/src/geo_t/helpers/date.gleam index 60f603b..b622d2d 100644 --- a/src/geo_t/helpers/date.gleam +++ b/src/geo_t/helpers/date.gleam @@ -13,6 +13,9 @@ pub fn to_iso8601(d: Date) -> String @external(javascript, "../../date_ffi.mjs", "from_unix") pub fn from_unix(a: Int) -> Result(Date, Nil) +@external(javascript, "../../date_ffi.mjs", "to_unix") +pub fn to_unix(a: Date) -> Int + @external(javascript, "../../date_ffi.mjs", "now") pub fn now() -> Date diff --git a/src/geo_t/web.gleam b/src/geo_t/web.gleam index f084558..358aad1 100644 --- a/src/geo_t/web.gleam +++ b/src/geo_t/web.gleam @@ -1,11 +1,15 @@ import gleam/option.{None, Option, Some} +import gleam/order import gleam/string +import gleam/io import gleam/javascript/promise import lustre import lustre/element/html import lustre/effect.{Effect} import lustre/element import plinth/javascript/storage.{Storage} +import geo_t/helpers/timers +import geo_t/helpers/date import geo_t/config.{Config} import geo_t/web/login_view import geo_t/web/installations_view @@ -15,6 +19,8 @@ import geo_t/pump_api/api.{ApiError} import geo_t/pump_api/auth/api as auth_api import geo_t/web/auth.{AuthInfo, AuthInfoStorage} +const auth_update_check_interval = 10_000 + pub fn main() { let app = lustre.application(init, update, view) let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil) @@ -41,6 +47,8 @@ pub type Msg { LoginResult(Result(User, ApiError)) InstallationsView(installations_view.Msg) PumpView(pump_view.Msg) + AuthRefreshCheck + RefreshLogin } fn init(_) { @@ -52,6 +60,9 @@ fn init(_) { Error(_) -> None } + let auth_refresh_effect = + effect.from(fn(dispatch) { auth_refresh_check_timer(dispatch) }) + let #(inst_model, inst_effect) = case auth_info { Some(info) -> update_installations( @@ -73,13 +84,15 @@ fn init(_) { logging_in: False, logged_in: option.is_some(auth_info), ), - inst_effect, + effect.batch([auth_refresh_effect, inst_effect]), ) } fn update(model: Model, msg: Msg) { case msg { - LoginView(login_view.AttemptLogin) -> { + AuthRefreshCheck -> #(model, auth_refresh_check(model)) + + RefreshLogin | LoginView(login_view.AttemptLogin) -> { #( Model( ..model, @@ -164,11 +177,11 @@ fn view(model: Model) { html.div( [], [ - case model.logged_in { - False -> + case model.auth { + None -> login_view.view(model.login) |> element.map(LoginView) - True -> { + Some(_) -> { case model.pump { None -> installations_view.view(model.installations) @@ -183,6 +196,46 @@ fn view(model: Model) { ) } +fn auth_refresh_check_timer(dispatch) { + timers.set_timeout( + fn() { dispatch(AuthRefreshCheck) }, + auth_update_check_interval, + ) + + Nil +} + +fn auth_refresh_check(model: Model) { + use dispatch <- effect.from() + + auth_refresh_check_timer(dispatch) + + case model.auth { + Some(auth) -> { + let now = date.now() + let assert Ok(last_valid_time) = + date.from_unix( + date.to_unix(auth.user.tokens.access_token_expiry) - model.config.api_auth_token_refresh_diff, + ) + + case date.compare(now, last_valid_time) { + order.Gt -> { + io.println("[AuthCheck] Auth expiring soon, refreshing...") + dispatch(RefreshLogin) + Nil + } + + order.Lt | order.Eq -> { + io.println("[AuthCheck] Auth not expiring yet, no need to refresh.") + } + } + + Nil + } + None -> Nil + } +} + fn login(model: Model) -> Effect(Msg) { use dispatch <- effect.from()