Finalize frontend of session management

This commit is contained in:
2025-06-26 19:45:20 +03:00
parent 169c4faa7d
commit 3da7f369a5
9 changed files with 235 additions and 149 deletions

View File

@@ -1,6 +1,7 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "base64" gem "base64"
gem "erb"
gem "json" gem "json"
gem "net-http" gem "net-http"
gem "sequel" gem "sequel"

View File

@@ -3,6 +3,7 @@ GEM
specs: specs:
base64 (0.3.0) base64 (0.3.0)
bigdecimal (3.2.2) bigdecimal (3.2.2)
erb (5.0.1)
json (2.12.2) json (2.12.2)
logger (1.7.0) logger (1.7.0)
mustermann (3.0.3) mustermann (3.0.3)
@@ -38,6 +39,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
base64 base64
erb
json json
net-http net-http
sequel sequel

View File

@@ -7,6 +7,7 @@
name="viewport" name="viewport"
content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/> />
<meta name="signed_in" content="<%= @signed_in %>">
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link
@@ -15,6 +16,7 @@
/> />
<link rel="stylesheet" href="src/assets/style.css" /> <link rel="stylesheet" href="src/assets/style.css" />
<script type="module" src="src/js/accounts.js"></script> <script type="module" src="src/js/accounts.js"></script>
<script type="module" src="src/js/game.js"></script>
</head> </head>
<body> <body>
<div class="header"> <div class="header">
@@ -26,16 +28,16 @@
/> />
</a> </a>
<div class="header-right"> <div class="header-right">
<div class="signed_in">
<button class="account-button" id="logout-button">LOGOUT</button>
</div>
<div class="not_signed_i">
<button class="account-button" id="login-button">LOGIN</button> <button class="account-button" id="login-button">LOGIN</button>
<button class="account-button" id="signup-button">SIGN UP</button> <button class="account-button" id="signup-button">SIGN UP</button>
<button class="account-button" id="logout-button">LOGOUT</button> </div>
<button class="account-button" id="forgot-button">
FORGOT - DEBUG
</button>
</div> </div>
</div> </div>
<canvas id="main-canvas"></canvas> <canvas id="main-canvas"></canvas>
<script type="module" src="src/js/game.js"></script>
<div class="popup" id="popup"> <div class="popup" id="popup">
<span class="close" id="close">&times;</span> <span class="close" id="close">&times;</span>
<div class="popup-tab" id="login"> <div class="popup-tab" id="login">
@@ -45,6 +47,7 @@
<label for="pass">PASSWORD</label> <label for="pass">PASSWORD</label>
<input type="password" name="pass" placeholder="Password" required /> <input type="password" name="pass" placeholder="Password" required />
<button type="submit">LOGIN</button> <button type="submit">LOGIN</button>
<span class="link" id="forgot-link">Forgot password?</span>
<span class="info" id="login-info"></span> <span class="info" id="login-info"></span>
</form> </form>
</div> </div>
@@ -53,7 +56,13 @@
<label for="username">USERNAME</label> <label for="username">USERNAME</label>
<input type="text" name="username" placeholder="Username" required /> <input type="text" name="username" placeholder="Username" required />
<label for="email">EMAIL</label> <label for="email">EMAIL</label>
<input type="email" name="email" placeholder="Email" required /> <input
type="email"
name="email"
placeholder="Email"
required
autocapitalize="off"
/>
<label for="pass">PASSWORD</label> <label for="pass">PASSWORD</label>
<input type="password" name="pass" placeholder="Password" required /> <input type="password" name="pass" placeholder="Password" required />
<button type="submit">SIGN UP</button> <button type="submit">SIGN UP</button>
@@ -62,7 +71,14 @@
</div> </div>
<div class="popup-tab" id="forgot-pass"> <div class="popup-tab" id="forgot-pass">
<form id="forgot-form" class="form"> <form id="forgot-form" class="form">
<input type="email" name="email" placeholder="Email" /> <input
type="email"
name="email"
placeholder="Email"
required
autocapitalize="off"
autocomplete="off"
/>
<button type="submit">SUBMIT</button> <button type="submit">SUBMIT</button>
<span class="info" id="forgot-info"></span> <span class="info" id="forgot-info"></span>
</form> </form>

View File

@@ -1,7 +1,8 @@
# module for logging # module for logging
module Logman module Logman
def self.log(log) def self.log(log)
File.write("log/main.log", "[#{Time.now}] #{log}\n", mode: "a") file, line = caller(1, 1)[0].split(":")
File.write("log/main.log", "[#{Time.now}] {#{file}:#{line}} #{log}\n", mode: "a")
end end
def self.imp(log) def self.imp(log)

28
main.rb
View File

@@ -1,4 +1,5 @@
require "base64" require "base64"
require "erb"
require "json" require "json"
require "net/http" require "net/http"
require "sequel" require "sequel"
@@ -19,7 +20,7 @@ env_data.each_line do |line|
ENV_HASH[match[1]] = match[2] ENV_HASH[match[1]] = match[2]
end end
Logman.log ENV_HASH.inspect # Logman.log ENV_HASH.inspect
CODE_ENV = :dev CODE_ENV = :dev
@@ -36,8 +37,9 @@ set :public_folder, "public"
get "/" do get "/" do
session = Sessions.new request, response session = Sessions.new request, response
Logman.log session.message Logman.log session.message if session.message != ""
send_file "index.html" @signed_in = session.signed_in?.nil? ? false : true
ERB.new(File.read("index.erb")).result(binding)
end end
get "/debug" do get "/debug" do
@@ -48,7 +50,7 @@ end
post "/signup" do post "/signup" do
session = Sessions.new request, response session = Sessions.new request, response
uid = session["user"] uid = session["user"]
session.logout(uid) if uid.nil? || $active_users[uid].nil? session.logout unless uid.nil? || $active_users[uid].nil?
data = JSON.parse(request.body.read) data = JSON.parse(request.body.read)
if data["email"].nil? || data["pass"].nil? || data["username"].nil? if data["email"].nil? || data["pass"].nil? || data["username"].nil?
status 400 status 400
@@ -57,9 +59,14 @@ post "/signup" do
signup_status = Players.mk_player(data["username"], data["email"], data["pass"]) signup_status = Players.mk_player(data["username"], data["email"], data["pass"])
if signup_status[0] == 200 if signup_status[0] == 200
login_status = session.login(data["username"], data["pass"]) login_status = session.login(data["username"], data["pass"])
if login_status[0] == 200
status 200
return { "message" => login_status[1], "success" => "true" }.to_json
else
status login_status[0] status login_status[0]
return { "message" => login_status[1] }.to_json return { "message" => login_status[1] }.to_json
end end
end
status signup_status[0] status signup_status[0]
return { "message" => signup_status[1] }.to_json return { "message" => signup_status[1] }.to_json
end end
@@ -74,7 +81,7 @@ post "/login" do
data = JSON.parse(request.body.read) data = JSON.parse(request.body.read)
session = Sessions.new request, response session = Sessions.new request, response
uid = session["user"] uid = session["user"]
if $active_users[uid] && !session.logout(uid) if $active_users[uid] && !session.logout
status 500 status 500
return { "message" => "Internal server error when signing the existing session out!" }.to_json return { "message" => "Internal server error when signing the existing session out!" }.to_json
end end
@@ -83,9 +90,14 @@ post "/login" do
return { "message" => "Bad request made!" }.to_json return { "message" => "Bad request made!" }.to_json
end end
login_status = session.login(data["username"], data["pass"]) login_status = session.login(data["username"], data["pass"])
if login_status[0] == 200
status 200
return { "message" => login_status[1], "success" => "true" }.to_json
else
status login_status[0] status login_status[0]
return { "message" => login_status[1] }.to_json return { "message" => login_status[1] }.to_json
end end
end
post "/logout" do post "/logout" do
session = Sessions.new request, response session = Sessions.new request, response
@@ -94,7 +106,7 @@ post "/logout" do
status 400 status 400
return { "message" => "Not signed in!" }.to_json return { "message" => "Not signed in!" }.to_json
end end
unless session.logout(uid) unless session.logout
status 500 status 500
return { "message" => "Internal server error when signing you out!" }.to_json return { "message" => "Internal server error when signing you out!" }.to_json
end end
@@ -105,7 +117,7 @@ end
get "/logout" do get "/logout" do
session = Sessions.new request, response session = Sessions.new request, response
uid = session["user"] uid = session["user"]
session.logout(uid) unless $active_users[uid].nil? session.logout unless $active_users[uid].nil?
redirect "/" redirect "/"
end end
@@ -165,7 +177,7 @@ delete "/rm_player" do
status 400 status 400
return { "message" => "Not signed in!" }.to_json return { "message" => "Not signed in!" }.to_json
end end
if session.logout(uid) && Players.rm_player($active_users[uid]) if session.logout && Players.rm_player($active_users[uid])
status 200 status 200
return { "message" => "Sorry to see you go.." }.to_json return { "message" => "Sorry to see you go.." }.to_json
else else

View File

@@ -46,7 +46,7 @@ module Players
code = Array.new(24) { ALPHANUM.sample }.join code = Array.new(24) { ALPHANUM.sample }.join
DB["update Players set new_pass_code = ? where email = ?", code, email].update DB["update Players set new_pass_code = ? where email = ?", code, email].update
Logman.log "Pass req: #{email} & #{code}\n" Logman.log "Pass req: #{email} & #{code}"
true true
@@ -76,7 +76,7 @@ module Players
def self.authorized?(username, pass) def self.authorized?(username, pass)
digest = XXhash.xxh32(pass, ENV_HASH["SALT"]) digest = XXhash.xxh32(pass, ENV_HASH["SALT"])
Logman.log "Authorized: #{username} & #{digest}\n" Logman.log "Authorized: #{username} & #{digest}"
player = self[username] player = self[username]
player && player[:digest].to_i == digest.to_i ? player : false player && player[:digest].to_i == digest.to_i ? player : false
end end
@@ -93,7 +93,7 @@ module Players
rm_player(player[:username]) if player[:created_at] + 24 * 60 * 60 < Time.now rm_player(player[:username]) if player[:created_at] + 24 * 60 * 60 < Time.now
end end
rescue StandardError => e rescue StandardError => e
Logman.log "Thread error: #{e.message}\n" Logman.log "Thread error: #{e.message}"
ensure ensure
sleep 60 * 60 sleep 60 * 60
end end

View File

@@ -21,6 +21,27 @@ button {
font-size: 18px; font-size: 18px;
} }
input {
outline: none;
font-family: "WDXL Lubrifont JP N", sans-serif;
font-size: 18px;
background-color: white;
color: #1b262c;
border: none;
}
.link {
color: #90bdd9;
text-decoration: none;
cursor: pointer;
font-family: "WDXL Lubrifont JP N", sans-serif;
}
.link:hover {
color: #adcee3;
text-decoration: underline;
}
.logo { .logo {
height: 50px; height: 50px;
position: relative; position: relative;
@@ -72,9 +93,10 @@ button {
.info { .info {
margin-bottom: 10px; margin-bottom: 10px;
color: red; color: white;
font-weight: bold; font-weight: bold;
opacity: none; opacity: none;
font-family: "WDXL Lubrifont JP N", sans-serif;
} }
.info:empty::before { .info:empty::before {
@@ -132,6 +154,21 @@ button {
margin-right: 20px; margin-right: 20px;
} }
.account-button {
background: none;
border: none;
cursor: pointer;
color: #90bdd9;
font-size: 23px;
margin-right: 10px;
font-family: "WDXL Lubrifont JP N", sans-serif;
}
.account-button:hover {
color: #adcee3;
text-decoration: underline;
}
.pixelart { .pixelart {
image-rendering: pixelated; image-rendering: pixelated;
image-rendering: crisp-edges; image-rendering: crisp-edges;

View File

@@ -1,130 +1,145 @@
window.onload = async () => { window.onload = async () => {
const login_form = document.getElementById("login-form"); const popup = document.getElementById("popup");
const login_button = document.getElementById("login-button"); const loginSection = document.getElementById("login");
const signup_form = document.getElementById("signup-form"); const signupSection = document.getElementById("signup");
const signup_button = document.getElementById("signup-button"); const forgotPassSection = document.getElementById("forgot-pass");
const logout_button = document.getElementById("logout-button"); const resetPassSection = document.getElementById("reset-pass");
const forgot_button = document.getElementById("forgot-button");
const forgot_form = document.getElementById("forgot-form");
forgot_button.onclick = () => { const loginForm = document.getElementById("login-form");
document.getElementById("popup").classList.add("active"); const signupForm = document.getElementById("signup-form");
document.getElementById("forgot-pass").classList.add("active"); const forgotForm = document.getElementById("forgot-form");
document.getElementById("login").classList.remove("active"); const resetForm = document.getElementById("reset-form");
document.getElementById("signup").classList.remove("active");
document.getElementById("reset-pass").classList.remove("active"); const loginInfo = document.getElementById("login-info");
const signupInfo = document.getElementById("signup-info");
const forgotInfo = document.getElementById("forgot-info");
const resetInfo = document.getElementById("reset-info");
const loginButton = document.getElementById("login-button");
const signupButton = document.getElementById("signup-button");
const logoutButton = document.getElementById("logout-button");
const forgotButton = document.getElementById("forgot-link");
const closeButton = document.getElementById("close");
const signedInMeta = document.querySelector('meta[name="signed_in"]');
const isSignedIn = signedInMeta?.content === "true";
if (isSignedIn) {
loginButton.style.display = "none";
signupButton.style.display = "none";
} else {
logoutButton.style.display = "none";
}
const showPopup = (section) => {
popup.classList.add("active");
loginSection.classList.remove("active");
signupSection.classList.remove("active");
forgotPassSection.classList.remove("active");
resetPassSection.classList.remove("active");
if (section) section.classList.add("active");
}; };
forgot_form.onsubmit = async (e) => { const hidePopup = () => {
popup.classList.remove("active");
loginSection.classList.remove("active");
signupSection.classList.remove("active");
forgotPassSection.classList.remove("active");
resetPassSection.classList.remove("active");
};
loginButton?.addEventListener("click", () => showPopup(loginSection));
signupButton?.addEventListener("click", () => showPopup(signupSection));
logoutButton?.addEventListener(
"click",
() => (window.location.href = "/logout"),
);
forgotButton?.addEventListener("click", () => showPopup(forgotPassSection));
closeButton?.addEventListener("click", hidePopup);
// TODO: if succesful hide form and popup success message
loginForm?.addEventListener("submit", async (e) => {
e.preventDefault(); e.preventDefault();
const email = forgot_form.email.value; const { username, pass } = loginForm;
const res = await fetch("/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: username.value, pass: pass.value }),
});
const data = await res.json();
loginInfo.innerText = data.message;
if (data.success == "true") {
loginButton.style.display = "none";
signupButton.style.display = "none";
logoutButton.style.display = "block";
}
});
// TODO: if succesful hide form and popup success message
signupForm?.addEventListener("submit", async (e) => {
e.preventDefault();
const { username, email, pass } = signupForm;
const res = await fetch("/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: username.value,
email: email.value,
pass: pass.value,
}),
});
const data = await res.json();
signupInfo.innerText = data.message;
if (data.success == "true") {
loginButton.style.display = "none";
signupButton.style.display = "none";
logoutButton.style.display = "block";
}
});
forgotForm?.addEventListener("submit", async (e) => {
e.preventDefault();
const email = forgotForm.email.value;
const res = await fetch("/forgot_password", { const res = await fetch("/forgot_password", {
method: "POST", method: "POST",
headers: { headers: { "Content-Type": "application/json" },
"Content-Type": "application/json",
},
body: JSON.stringify({ email }), body: JSON.stringify({ email }),
}); });
const data = await res.json(); const data = await res.json();
document.getElementById("forgot-info").innerHTML = data.message; forgotInfo.innerHTML = data.message;
};
logout_button.onclick = () => {
window.location.href = "/logout";
};
login_button.onclick = () => {
document.getElementById("popup").classList.add("active");
document.getElementById("login").classList.add("active");
document.getElementById("signup").classList.remove("active");
document.getElementById("forgot-pass").classList.remove("active");
document.getElementById("reset-pass").classList.remove("active");
};
signup_button.onclick = () => {
document.getElementById("popup").classList.add("active");
document.getElementById("signup").classList.add("active");
document.getElementById("login").classList.remove("active");
document.getElementById("forgot-pass").classList.remove("active");
document.getElementById("reset-pass").classList.remove("active");
};
document.getElementById("close").onclick = () => {
document.getElementById("popup").classList.remove("active");
document.getElementById("login").classList.remove("active");
document.getElementById("signup").classList.remove("active");
document.getElementById("forgot-pass").classList.remove("active");
document.getElementById("reset-pass").classList.remove("active");
};
login_form.onsubmit = async (e) => {
e.preventDefault();
const username = login_form.username.value;
const pass = login_form.pass.value;
let response = await fetch("/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, pass }),
}); });
response = await response.json();
document.getElementById("login-info").innerText = response.message;
};
signup_form.onsubmit = async (e) => {
e.preventDefault();
const username = signup_form.username.value;
const email = signup_form.email.value;
const pass = signup_form.pass.value;
let response = await fetch("/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, email, pass }),
});
response = await response.json();
document.getElementById("signup-info").innerText = response.message;
};
// Handle reset code in URL
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const reset_code = params.get("reset_code"); const resetCode = params.get("reset_code");
if (reset_code) {
let response = await fetch("/pass_reset?", { if (resetCode) {
const res = await fetch("/pass_reset?", {
method: "POST", method: "POST",
headers: { headers: { "Content-Type": "application/json" },
"Content-Type": "application/json", body: JSON.stringify({ code: resetCode }),
},
body: JSON.stringify({ code: reset_code }),
}); });
let status = response.status;
if (status == 200) { if (res.status === 200) {
document.getElementById("popup").classList.add("active"); showPopup(resetPassSection);
document.getElementById("reset-pass").classList.add("active");
document.getElementById("login").classList.remove("active"); resetForm?.addEventListener("submit", async (e) => {
document.getElementById("signup").classList.remove("active");
document.getElementById("forgot-pass").classList.remove("active");
document.getElementById("reset-form").onsubmit = async (e) => {
e.preventDefault(); e.preventDefault();
const pass = document.getElementById("reset-form").pass.value; const pass = resetForm.pass.value;
const pass_confirm = const passConfirm = resetForm.pass_confirm.value;
document.getElementById("reset-form").pass_confirm.value;
if (pass != pass_confirm) { if (pass !== passConfirm) {
document.getElementById("reset-info").innerText = resetInfo.innerText = "Passwords do not match";
"Passwords do not match";
return; return;
} }
let response = await fetch("/reset_password/" + reset_code, {
const res = await fetch(`/reset_password/${resetCode}`, {
method: "POST", method: "POST",
headers: { headers: { "Content-Type": "application/json" },
"Content-Type": "application/json",
},
body: JSON.stringify({ pass }), body: JSON.stringify({ pass }),
}); });
response = await response.json(); const data = await res.json();
document.getElementById("reset-info").innerText = response.message; resetInfo.innerText = data.message;
}; });
} else { } else {
window.location.href = "/"; window.location.href = "/";
} }

View File

@@ -5,32 +5,32 @@ class Sessions
@response = response @response = response
end end
def signed_in?(code) def signed_in?
$active_users[code] $active_users[self["user"]]
end end
def logout(uid) def logout
uid = self["user"]
$active_users.delete(uid) $active_users.delete(uid)
delete("user") delete("user")
DB["delete from SignedInUsers where code = ?", uid].delete DB["delete from SignedInUsers where code = ?", uid].delete
true true
rescue Sequel::Error => e rescue Sequel::Error => e
Logman.log "DB Error: #{e.message}\n" Logman.log "DB Error: #{e.message}"
false false
end end
def login(username, pass) def login(username, pass)
Logman.log "Logging in: #{username} & #{pass} #{ENV_HASH["SALT"]}\n" Logman.log "Logging in: #{username} & #{pass} #{ENV_HASH["SALT"]}"
player = Players.authorized?(username, pass) player = Players.authorized?(username, pass)
if player if player
code = Array.new(24) { ALPHANUM.sample }.join code = Array.new(24) { ALPHANUM.sample }.join
self["user", code] = code self["user"] = code
$active_users[code] = username $active_users[code] = username
begin begin
DB["insert into SignedInUsers (code, player) values (?, ?)", code, username].insert DB["insert into SignedInUsers (code, player) values (?, ?)", code, username].insert
rescue Sequel::Error => e rescue Sequel::Error => e
Logman.log "DB Error: #{e.message}\n" Logman.log "DB Error: #{e.message}"
return [500, "Internal server error when signing you in!"] return [500, "Internal server error when signing you in!"]
end end
return [200, "Remember to verify your email!"] unless Players.verified?(username) return [200, "Remember to verify your email!"] unless Players.verified?(username)
@@ -40,7 +40,7 @@ class Sessions
end end
end end
def []=(key, uid, val) def []=(key, val)
session = @request.cookies["session"] session = @request.cookies["session"]
session = session.nil? ? "{}" : Zlib::Inflate.inflate(Base64.decode64(session)) session = session.nil? ? "{}" : Zlib::Inflate.inflate(Base64.decode64(session))
session = JSON.parse(session) session = JSON.parse(session)
@@ -51,21 +51,23 @@ class Sessions
value: encoded, value: encoded,
path: "/", path: "/",
expires: Time.now + 360 * 24 * 60 * 60) expires: Time.now + 360 * 24 * 60 * 60)
uid = session["user"]
DB["UPDATE SignedInUsers SET last_used_at = CURRENT_TIMESTAMP WHERE code = ?", uid].update if uid DB["UPDATE SignedInUsers SET last_used_at = CURRENT_TIMESTAMP WHERE code = ?", uid].update if uid
rescue JSON::ParserError, Zlib::Error rescue JSON::ParserError, Zlib::Error
@response.delete_cookie("session") @response.delete_cookie("session")
rescue Sequel::Error => e rescue Sequel::Error => e
Logman.log "DB Error: #{e.message} when updating last_used_at for #{uid}\n" Logman.log "DB Error: #{e.message} when updating last_used_at for #{uid}"
end end
def [](key, uid: nil) def [](key)
session = @request.cookies["session"] session = @request.cookies["session"]
session = session.nil? ? "{}" : Zlib::Inflate.inflate(Base64.decode64(session)) session = session.nil? ? "{}" : Zlib::Inflate.inflate(Base64.decode64(session))
session = JSON.parse(session) session = JSON.parse(session)
begin begin
uid = session["user"]
DB["UPDATE SignedInUsers SET last_used_at = CURRENT_TIMESTAMP WHERE code = ?", uid].update if uid DB["UPDATE SignedInUsers SET last_used_at = CURRENT_TIMESTAMP WHERE code = ?", uid].update if uid
rescue Sequel::Error => e rescue Sequel::Error => e
Logman.log "DB Error: #{e.message} when updating last_used_at for #{uid}\n" Logman.log "DB Error: #{e.message} when updating last_used_at for #{uid}"
end end
session[key] session[key]
rescue JSON::ParserError, Zlib::Error rescue JSON::ParserError, Zlib::Error
@@ -121,16 +123,16 @@ class Sessions
begin begin
DB[:SignedInUsers].where(code: session[:code]).delete DB[:SignedInUsers].where(code: session[:code]).delete
rescue StandardError => e rescue StandardError => e
Logman.log "Thread DB error: #{e.message} on #{session[:code]} for #{session[:player]}\n" Logman.log "Thread DB error: #{e.message} on #{session[:code]} for #{session[:player]}"
end end
$active_users.delete(session[:code]) $active_users.delete(session[:code])
puts "Auto-logged out: #{session[:player]} (expired session)" puts "Auto-logged out: #{session[:player]} (expired session)"
end end
rescue StandardError => e rescue StandardError => e
Logman.log "Thread error: #{e.message}\n" Logman.log "Thread error: #{e.message}"
ensure ensure
sleep 60 * 60 * 24 sleep 60 * 60 * 24
Logman.log "Thread sleeping\n" Logman.log "Thread sleeping"
end end
end end
end end