Move login call to native
parent
dbfd56e727
commit
49f97e2895
|
@ -41,17 +41,6 @@ div.p {
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.error {
|
|
||||||
display: none;
|
|
||||||
color: red;
|
|
||||||
font-weight: 500;
|
|
||||||
word-wrap: break-word;
|
|
||||||
user-select: text;
|
|
||||||
background: white;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
color: var(--fg-color);
|
color: var(--fg-color);
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
@ -106,22 +95,3 @@ input.m-input:focus {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#folder-list {
|
|
||||||
margin: 15px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-choose {
|
|
||||||
font-size: 1.1em;
|
|
||||||
margin-top: 4px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1em auto;
|
|
||||||
gap: 1.2em;
|
|
||||||
line-height: 1.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"] {
|
|
||||||
width: 2em;
|
|
||||||
height: 2em;
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,8 +26,6 @@
|
||||||
</button>
|
</button>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<p id="conn-error" class="error"></p>
|
|
||||||
|
|
||||||
<a class="m-button link" href="https://memories.gallery/install/">
|
<a class="m-button link" href="https://memories.gallery/install/">
|
||||||
I don't have a server
|
I don't have a server
|
||||||
</a>
|
</a>
|
||||||
|
@ -36,7 +34,6 @@
|
||||||
<script>
|
<script>
|
||||||
const urlBox = document.getElementById("server-url");
|
const urlBox = document.getElementById("server-url");
|
||||||
const loginButton = document.getElementById("login");
|
const loginButton = document.getElementById("login");
|
||||||
const connError = document.getElementById("conn-error");
|
|
||||||
|
|
||||||
function validateUrl(url) {
|
function validateUrl(url) {
|
||||||
try {
|
try {
|
||||||
|
@ -86,10 +83,6 @@
|
||||||
|
|
||||||
// Login button click handler
|
// Login button click handler
|
||||||
loginButton.addEventListener("click", async () => {
|
loginButton.addEventListener("click", async () => {
|
||||||
const url = getMemoriesUrl();
|
|
||||||
url.pathname += "api/describe";
|
|
||||||
const urlStr = url.toString();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
urlBox.disabled = true;
|
urlBox.disabled = true;
|
||||||
loginButton.disabled = true;
|
loginButton.disabled = true;
|
||||||
|
@ -98,20 +91,17 @@
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
// Get API description
|
// Login signal
|
||||||
const res = await fetch(urlStr, {
|
const encUrl = encodeURIComponent(encodeURIComponent(getMemoriesUrl().toString()));
|
||||||
|
await fetch(`http://127.0.0.1/api/login/${encUrl}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
// API is fine, redirect to login page
|
// API is fine, redirect to login page
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
window.nativex.login(data.baseUrl, data.loginFlowUrl);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// CORS request, we know nothing about the server
|
// unreachable?
|
||||||
connError.innerText = `Failed to fetch server info.\nIs Memories installed and enabled?\n${urlStr}`;
|
|
||||||
connError.style.display = "block";
|
|
||||||
} finally {
|
} finally {
|
||||||
urlBox.disabled = false;
|
urlBox.disabled = false;
|
||||||
loginButton.disabled = false;
|
loginButton.disabled = false;
|
||||||
|
|
|
@ -43,6 +43,8 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
object API {
|
object API {
|
||||||
|
val LOGIN = Regex("^/api/login/.+$")
|
||||||
|
|
||||||
val DAYS = Regex("^/api/days$")
|
val DAYS = Regex("^/api/days$")
|
||||||
val DAY = Regex("^/api/days/\\d+$")
|
val DAY = Regex("^/api/days/\\d+$")
|
||||||
|
|
||||||
|
@ -93,12 +95,6 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
|
||||||
fun login(baseUrl: String?, loginFlowUrl: String?) {
|
|
||||||
if (baseUrl == null || loginFlowUrl == null) return;
|
|
||||||
account.login(baseUrl, loginFlowUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun logout() {
|
fun logout() {
|
||||||
account.loggedOut()
|
account.loggedOut()
|
||||||
|
@ -227,7 +223,9 @@ class NativeX(private val mCtx: MainActivity) {
|
||||||
val path = request.url.path ?: return makeErrorResponse()
|
val path = request.url.path ?: return makeErrorResponse()
|
||||||
|
|
||||||
val parts = path.split("/").toTypedArray()
|
val parts = path.split("/").toTypedArray()
|
||||||
return if (path.matches(API.DAYS)) {
|
return if (path.matches(API.LOGIN)) {
|
||||||
|
makeResponse(account.login(URLDecoder.decode(parts[3], "UTF-8")))
|
||||||
|
} else if (path.matches(API.DAYS)) {
|
||||||
makeResponse(query.getDays())
|
makeResponse(query.getDays())
|
||||||
} else if (path.matches(API.DAY)) {
|
} else if (path.matches(API.DAY)) {
|
||||||
makeResponse(query.getDay(parts[3].toLong()))
|
makeResponse(query.getDay(parts[3].toLong()))
|
||||||
|
|
|
@ -9,7 +9,6 @@ import androidx.media3.common.util.UnstableApi
|
||||||
import gallery.memories.MainActivity
|
import gallery.memories.MainActivity
|
||||||
import gallery.memories.R
|
import gallery.memories.R
|
||||||
import io.github.g00fy2.versioncompare.Version
|
import io.github.g00fy2.versioncompare.Version
|
||||||
import java.net.SocketTimeoutException
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class AccountService(private val mCtx: MainActivity, private val mHttp: HttpService) {
|
class AccountService(private val mCtx: MainActivity, private val mHttp: HttpService) {
|
||||||
|
@ -19,42 +18,59 @@ class AccountService(private val mCtx: MainActivity, private val mHttp: HttpServ
|
||||||
|
|
||||||
private val store = SecureStorage(mCtx)
|
private val store = SecureStorage(mCtx)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the first request to log in
|
||||||
|
* @param url The URL of the Nextcloud server
|
||||||
|
*/
|
||||||
|
fun login(url: String) {
|
||||||
|
try {
|
||||||
|
Log.v(TAG, "login: Connecting to ${url}api/describe")
|
||||||
|
mHttp.setBaseUrl(url)
|
||||||
|
val res = mHttp.getApiDescription()
|
||||||
|
|
||||||
|
if (res.code != 200) {
|
||||||
|
throw Exception("${url}api/describe (status ${res.code})")
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = mHttp.bodyJson(res) ?: throw Exception("Failed to parse API description")
|
||||||
|
|
||||||
|
val baseUrl = body.getString("baseUrl")
|
||||||
|
val loginFlowUrl = body.getString("loginFlowUrl")
|
||||||
|
loginFlow(baseUrl, loginFlowUrl)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast("Error: ${e.message}")
|
||||||
|
throw Exception("Failed to connect to server: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login to a server
|
* Login to a server
|
||||||
* @param baseUrl The base URL of the server
|
* @param baseUrl The base URL of the server
|
||||||
* @param loginFlowUrl The login flow URL
|
* @param loginFlowUrl The login flow URL
|
||||||
|
* @throws Exception If the login flow failed
|
||||||
*/
|
*/
|
||||||
fun login(baseUrl: String, loginFlowUrl: String) {
|
fun loginFlow(baseUrl: String, loginFlowUrl: String) {
|
||||||
try {
|
val res = mHttp.postLoginFlow(loginFlowUrl)
|
||||||
val res = mHttp.postLoginFlow(loginFlowUrl)
|
|
||||||
|
|
||||||
// Check if 200 was received
|
// Check if 200 was received
|
||||||
if (res.code != 200) {
|
if (res.code != 200) {
|
||||||
throw Exception("Server returned a ${res.code} status code. Please check your reverse proxy configuration and overwriteprotocol is correct.")
|
throw Exception("Login flow returned a ${res.code} status code. Check your reverse proxy configuration and overwriteprotocol is correct.")
|
||||||
}
|
|
||||||
|
|
||||||
// Get body as JSON
|
|
||||||
val body = mHttp.bodyJson(res) ?: throw Exception("Failed to parse login flow response")
|
|
||||||
|
|
||||||
// Parse response body as JSON
|
|
||||||
val pollObj = body.getJSONObject("poll")
|
|
||||||
val pollToken = pollObj.getString("token")
|
|
||||||
val pollUrl = pollObj.getString("endpoint")
|
|
||||||
val loginUrl = body.getString("login")
|
|
||||||
|
|
||||||
// Open login page in browser
|
|
||||||
mCtx.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(loginUrl)))
|
|
||||||
|
|
||||||
// Start polling in background
|
|
||||||
Thread { pollLogin(pollUrl, pollToken, baseUrl) }.start()
|
|
||||||
} catch (e: SocketTimeoutException) {
|
|
||||||
toast("Failed to connect to login flow URL")
|
|
||||||
return
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "login: ", e)
|
|
||||||
toast(e.message ?: "Unknown error")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get body as JSON
|
||||||
|
val body = mHttp.bodyJson(res) ?: throw Exception("Failed to parse login flow response")
|
||||||
|
|
||||||
|
// Parse response body as JSON
|
||||||
|
val pollObj = body.getJSONObject("poll")
|
||||||
|
val pollToken = pollObj.getString("token")
|
||||||
|
val pollUrl = pollObj.getString("endpoint")
|
||||||
|
val loginUrl = body.getString("login")
|
||||||
|
|
||||||
|
// Open login page in browser
|
||||||
|
mCtx.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(loginUrl)))
|
||||||
|
|
||||||
|
// Start polling in background
|
||||||
|
Thread { pollLogin(pollUrl, pollToken, baseUrl) }.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue