Files
LCE-Hub-LCE_Emerald_Launcher/src/services/LceOnlineService.ts
T

309 lines
7.9 KiB
TypeScript

const SESSION_KEY = "lceonline_session";
const SOCIAL_BASE_URL = "https://social.mclegacyedition.xyz";
const AUTH_BASE_URL = "https://auth.mclegacyedition.xyz"; //neo: yeah bro im hardcoding all three
import { TauriService } from "./TauriService";
export interface LceOnlineAccount {
username: string;
displayName: string;
}
export interface SessionData {
accessToken: string;
account: LceOnlineAccount;
}
export interface InviteEntry {
inviteid: string;
from: { uuid: string; displayName: string; username: string };
sessionid: string;
}
export interface SocialEntry {
username: string;
displayName: string;
uuid: string;
};
export interface SocialList {
friends: SocialEntry[];
friendRequests: SocialEntry[];
blocked: SocialEntry[];
}
export class LceOnlineService {
private _session: SessionData | null = null;
private baseUrl: string = SOCIAL_BASE_URL;
private _listeners: Array<() => void> = [];
constructor() {
this.loadSession();
if (this._session) {
this.refreshToken();
}
}
onSessionChange(listener: () => void): () => void {
this._listeners.push(listener);
return () => {
this._listeners = this._listeners.filter((l) => l !== listener);
};
}
private _notify() {
for (const l of this._listeners) l();
}
get signedIn(): boolean {
return this._session !== null;
}
get account(): LceOnlineAccount | null {
return this._session?.account || null;
}
get displayUsername(): string {
if (!this._session) return "Not signed in";
return (
this._session.account.displayName ||
this._session.account.username ||
"Unknown"
);
}
get accessToken(): string | null {
return this._session?.accessToken || null;
}
logoutLocal(): void {
this._session = null;
this.saveSession();
this._notify();
}
async login(username: string, password: string): Promise<void> {
const res = await this.request<string>(
"POST",
"/login",
`${username}:${password}`,
AUTH_BASE_URL,
);
const text = typeof res === "string" ? res : "";
if (!text.startsWith("-")) {
throw new Error(text || "Login failed");
}
const token = text.split(":")[1];
await this.loginWithTokenAndFetchAccount(token);
}
async register(username: string, password: string): Promise<void> {
const res = await this.request<string>(
"POST",
"/register",
`${username}:${password}`,
AUTH_BASE_URL,
);
const text = typeof res === "string" ? res : "";
if (!text.startsWith("-")) {
throw new Error(text || "Registration failed");
}
const token = text.split(":")[1];
await this.loginWithTokenAndFetchAccount(token);
}
loginWithToken(token: string, username?: string) {
const name = username || "Player";
this._session = {
accessToken: token,
account: { username: name, displayName: name },
};
this.saveSession();
this._notify();
}
async loginWithTokenAndFetchAccount(token: string): Promise<void> {
this._session = {
accessToken: token,
account: { username: "Player", displayName: "Player" },
};
this.saveSession();
this._notify();
try {
const raw: string = await this.request<string>("POST", "/accountinfo");
if (typeof raw === "string" && raw.startsWith("-")) {
const username = raw.slice(1);
this._session!.account = { username, displayName: username };
this.saveSession();
this._notify();
}
} catch (e) {
console.warn("Failed to fetch account info", e);
}
}
private loadSession() {
try {
const data = localStorage.getItem(SESSION_KEY);
if (data) {
this._session = JSON.parse(data);
}
} catch (e) {
console.warn("Failed to load LCE Online session", e);
}
}
private saveSession() {
if (this._session) {
localStorage.setItem(SESSION_KEY, JSON.stringify(this._session));
} else {
localStorage.removeItem(SESSION_KEY);
}
}
private async request<T = any>(
method: string,
path: string,
body?: string | null,
baseUrl?: string,
): Promise<T> {
const headers: Record<string, string> = {
Accept: "text/plain, application/json",
"User-Agent": "MCLCE-LCEOnline/1.0",
};
if (body) {
headers["Content-Type"] = "text/plain";
}
if (this._session?.accessToken) {
headers["Authorization"] = `Bearer ${this._session.accessToken}`;
}
const url = `${baseUrl || this.baseUrl}${path}`;
let res;
try {
res = await TauriService.httpProxyRequest(
method,
url,
body ?? null,
headers,
);
} catch (e) {
throw new Error(`Network error when calling ${path}: ${e}`);
}
let data;
try {
data = res.body ? JSON.parse(res.body) : {};
} catch {
data = res.body ?? {};
}
if (res.status >= 400) {
const errorMsg =
data.message ||
data.detail ||
data.title ||
data.error ||
data ||
`HTTP ${res.status}`;
throw new Error(errorMsg);
}
return data;
}
async refreshToken(): Promise<boolean> {
if (!this._session) return false;
try {
const res = await this.request<string>("POST", "/refreshtoken", null, AUTH_BASE_URL);
if (typeof res === "string" && res.startsWith("-")) {
const [username, token] = res.slice(1).split(":");
this._session.accessToken = token;
this._session.account = { username, displayName: username };
this.saveSession();
this._notify();
return true;
}
this.logoutLocal();
return false;
} catch (e) {
console.warn("Failed to refresh token", e);
this.logoutLocal()
return false;
}
}
async getSocialLists() {
const res = await this.request<SocialList>("GET", "/getSocialLists", null);
if (typeof res === "string") throw new Error(res);
return {
friends: res?.friends ?? [],
requests: res?.friendRequests ?? [],
blocked: res?.blocked ?? [],
};
}
async sendFriendRequest(target: string): Promise<void> {
const res = await this.request<string>("POST", "/sendrequest", target);
if (typeof res === "string" && res !== "Successfully Sent Friend Request") {
throw new Error(res);
}
}
async acceptFriendRequest(from: string): Promise<void> {
const res = await this.request<string>("POST", "/acceptrequest", from);
if (typeof res === "string" && res !== "1") {
throw new Error(res);
}
}
async declineFriendRequest(from: string): Promise<void> {
const res = await this.request<string>("POST", "/declinerequest", from);
if (typeof res === "string" && res !== "1") {
throw new Error(res);
}
}
async removeFriend(from: string): Promise<void> {
const res = await this.request<string>("POST", "/removefriend", from);
if (typeof res === "string") {
throw new Error(res);
}
}
async sendInvite(target: string): Promise<void> {
const res = await this.request<string>("POST", "/invite", target);
if (typeof res === "string" && res !== "Invite Sent") {
throw new Error(res);
}
}
async acceptInvite(from: string): Promise<string> {
const res = await this.request<string>("POST", "/acceptinvite", from);
if (typeof res !== "string") throw new Error("Failed to accept invite");
return res;
}
async declineInvite(from: string): Promise<void> {
try {
await this.request("POST", "/declineinvite", from);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "";
if (msg !== "Declined Invite") throw e;
}
}
async getInvites(): Promise<
Array<{
inviteid: string;
from: { uuid: string; displayName: string; username: string };
sessionid: string;
}>
> {
const res = await this.request<string>("GET", "/getinvites", null);
return Array.isArray(res) ? res : [];
}
}
export const lceOnlineService = new LceOnlineService();