feat(android): LCEOnline auth

This commit is contained in:
neoapps-dev 2026-08-11 14:43:28 +03:00
parent 84697a67e2
commit ca2fe8e258
8 changed files with 235 additions and 10 deletions

View file

@ -31,6 +31,11 @@
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
</activity>
<activity
android:name=".LceAuthActivity"
android:exported="false"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"

View file

@ -0,0 +1,123 @@
package com.emerald.legacy
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.os.Message
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.FrameLayout
import android.widget.ProgressBar
import androidx.appcompat.app.AppCompatActivity
class LceAuthActivity : AppCompatActivity() {
private lateinit var webView: WebView
private lateinit var progressBar: ProgressBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val authUrl = intent.getStringExtra("authUrl")
?: "https://mclegacyedition.xyz/internal/auth?appId=emerald_launcher"
progressBar = ProgressBar(this).apply {
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
webView = WebView(this).apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.databaseEnabled = true
settings.loadWithOverviewMode = true
settings.useWideViewPort = true
settings.setSupportMultipleWindows(true)
settings.javaScriptCanOpenWindowsAutomatically = true
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean = handleAuthUrl(url)
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean = handleAuthUrl(request.url.toString())
override fun onCreateWindow(
view: WebView,
isDialog: Boolean,
isUserGesture: Boolean,
resultMsg: Message
): Boolean {
val transport = resultMsg.obj as WebView.WebViewTransport
transport.webView = webView
resultMsg.sendToTarget()
return true
}
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
handleAuthUrl(url)
}
override fun onPageFinished(view: WebView, url: String) {
progressBar.visibility = View.GONE
}
}
}
val root = FrameLayout(this)
root.addView(
webView,
FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
val progressFrame = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
progressFrame.gravity = Gravity.CENTER
root.addView(progressBar, progressFrame)
setContentView(root)
webView.loadUrl(authUrl)
}
private fun handleAuthUrl(url: String): Boolean {
val uri = try {
Uri.parse(url)
} catch (e: Exception) {
return false
}
if (uri.scheme == "emerald" && uri.host == "lceonline") {
val token = uri.getQueryParameter("token")
if (token != null) {
setResult(RESULT_OK, Intent().putExtra("lceAuthToken", token))
} else {
setResult(RESULT_CANCELED)
}
finish()
return true
}
return false
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
if (webView.canGoBack()) {
webView.goBack()
} else {
setResult(Activity.RESULT_CANCELED)
super.onBackPressed()
}
}
override fun onDestroy() {
(webView.parent as? ViewGroup)?.removeView(webView)
webView.destroy()
super.onDestroy()
}
}

View file

@ -0,0 +1,37 @@
package com.emerald.legacy
import android.app.Activity
import android.content.Intent
import androidx.activity.result.ActivityResult
import app.tauri.annotation.ActivityCallback
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.Plugin
@TauriPlugin
class LceAuthPlugin(private val activity: Activity) : Plugin(activity) {
@Command
fun startAuth(invoke: Invoke) {
val intent = Intent(activity, LceAuthActivity::class.java)
intent.putExtra(
"authUrl",
"https://mclegacyedition.xyz/internal/auth?appId=emerald_launcher"
)
startActivityForResult(invoke, intent, "authResult")
}
@ActivityCallback
fun authResult(invoke: Invoke, result: ActivityResult) {
val token = if (result.resultCode == Activity.RESULT_OK) {
result.data?.getStringExtra("lceAuthToken")
} else {
null
}
if (!token.isNullOrEmpty()) {
invoke.resolveObject(token)
} else {
invoke.reject("Authentication cancelled or failed")
}
}
}

View file

@ -48,7 +48,7 @@ pub fn launch_bridge(instance_path: String, action: BridgeAction) -> Result<(),
&intent,
"addFlags",
"(I)Landroid/content/Intent;",
&[JValue::Int(0x10000000 as jint)],
&[JValue::Int(0x10000000 as jint)], //neo: FLAG_ACTIVITY_NEW_TASK
)?;
env.call_method(

35
src-tauri/src/lce_auth.rs Normal file
View file

@ -0,0 +1,35 @@
use tauri::plugin::{Builder, PluginHandle, TauriPlugin};
use tauri::Wry;
pub struct LceAuthState(pub PluginHandle<Wry>);
pub fn init() -> TauriPlugin<Wry> {
Builder::new("emerald-lce-auth")
.setup(|app, api| {
#[cfg(target_os = "android")]
{
use tauri::Manager;
let handle = api.register_android_plugin("com.emerald.legacy", "LceAuthPlugin")?;
app.manage(LceAuthState(handle));
}
Ok(())
})
.build()
}
#[tauri::command]
pub async fn start_lce_auth(app: tauri::AppHandle) -> Result<String, String> {
#[cfg(target_os = "android")]
{
use tauri::Manager;
let state = app.state::<LceAuthState>();
state
.0
.run_mobile_plugin_async("startAuth", ())
.await
.map_err(|e| e.to_string())
}
#[cfg(not(target_os = "android"))]
{
let _ = app;
Err("LCE Online auth is only supported on Android".into())
}
}

View file

@ -7,6 +7,7 @@ mod playtime;
mod platform;
mod networking;
mod workshop_server;
mod lce_auth;
#[cfg(target_os = "android")]
mod android_runtime;
mod commands;
@ -36,6 +37,7 @@ pub fn run() {
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_deep_link::init())
.plugin(webview_deep_link_interceptor())
.plugin(lce_auth::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_gamepad::init())
.plugin(tauri_plugin_opener::init());
@ -136,6 +138,7 @@ pub fn run() {
relay::stop_proxy,
relay::stop_all_proxies,
relay::join_game,
lce_auth::start_lce_auth,
plugins::get_plugins_dir,
plugins::list_directory,
plugins::create_plugin_dir,

View file

@ -7,6 +7,7 @@ import {
useGame,
} from "../../context/LauncherContext";
import ChooseInstanceModal from "../modals/ChooseInstanceModal";
import { usePlatform } from "../../hooks/usePlatform";
import { lceOnlineService, SocialEntry } from "../../services/LceOnlineService";
import { TauriService } from "../../services/TauriService";
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
@ -28,6 +29,7 @@ const LceOnlineView = memo(function LceOnlineView({
const { setActiveView, setIsUiHidden } = useUI();
const { animationsEnabled } = useConfig();
const { playPressSound, playBackSound } = useAudio();
const { isAndroid } = usePlatform();
const game = useGame();
const [isSignedIn, setIsSignedIn] = useState(lceOnlineService.signedIn);
const opened = useRef(false);
@ -80,25 +82,41 @@ const LceOnlineView = memo(function LceOnlineView({
if (!opened.current) {
opened.current = true;
new WebviewWindow('LCEOnline', {
url: "https://mclegacyedition.xyz/internal/auth?appId=emerald_launcher",
width: 400,
height: 570,
resizable: false,
title: 'Emerald Legacy Launcher - LCEOnline',
});
if (isAndroid) {
TauriService.startLceOnlineAuth()
.then((token) => {
lceOnlineService
.loginWithTokenAndFetchAccount(token)
.catch((e) => console.error(e));
setIsSignedIn(true);
})
.catch((e) => console.error("LCE Online auth failed", e));
} else {
new WebviewWindow('LCEOnline', {
url: "https://mclegacyedition.xyz/internal/auth?appId=emerald_launcher",
width: 400,
height: 570,
resizable: false,
title: 'Emerald Legacy Launcher - LCEOnline',
});
}
};
const unlisten = listen<string[]>('deep-link', async (event) => {
const authUrl = event.payload.find(u => u.startsWith('emerald://'));
if (!authUrl) return;
const token = new URL(authUrl).searchParams.get('token');
if (token) setIsSignedIn(true);
if (token) {
lceOnlineService
.loginWithTokenAndFetchAccount(token)
.catch((e) => console.error(e));
setIsSignedIn(true);
}
(await WebviewWindow.getByLabel('LCEOnline'))?.close();
});
return () => { unlisten.then(f => f()); };
}, [isSignedIn]);
}, [isSignedIn, isAndroid]);
useEffect(() => {
if (!addFriendTarget) return;

View file

@ -238,6 +238,10 @@ export class TauriService {
return invoke("plugin:opener|open_url", { url });
}
static async startLceOnlineAuth(): Promise<string> {
return invoke("start_lce_auth");
}
static async restartLauncher(): Promise<void> {
return invoke("restart_launcher");
}