[FEATURE][EXPERIMENTAL] New API

This commit is contained in:
Gravita 2024-06-09 16:02:52 +07:00
parent 7f6a645dd7
commit f6b3a62497
No known key found for this signature in database
GPG key ID: 543A8F335C9CD633
44 changed files with 838 additions and 19 deletions

View file

@ -1,4 +1,4 @@
{
"features": [],
"features": ["new-api"],
"info": []
}

View file

@ -1,10 +1,11 @@
package pro.gravit.launcher.base;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.core.api.model.UserPermissions;
import java.util.*;
public class ClientPermissions {
public class ClientPermissions implements UserPermissions {
public static final ClientPermissions DEFAULT = new ClientPermissions();
@LauncherNetworkAPI
private List<String> roles;
@ -28,6 +29,7 @@ public static ClientPermissions getSuperuserAccount() {
return perm;
}
@Override
public boolean hasRole(String role) {
return roles != null && roles.contains(role);
}
@ -46,6 +48,7 @@ public synchronized void compile() {
}
}
@Override
public boolean hasPerm(String action) {
if (available == null) {
compile();

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.profiles.PlayerProfile;
@ -67,7 +68,15 @@ public String getType() {
return "auth";
}
public static class OAuthRequestEvent {
public CurrentUserRequestEvent.UserInfo makeUserInfo() {
var userInfo = new CurrentUserRequestEvent.UserInfo();
userInfo.accessToken = accessToken;
userInfo.permissions = permissions;
userInfo.playerProfile = playerProfile;
return userInfo;
}
public static class OAuthRequestEvent implements AuthFeatureAPI.AuthToken {
public final String accessToken;
public final String refreshToken;
public final long expire;
@ -77,5 +86,20 @@ public OAuthRequestEvent(String accessToken, String refreshToken, long expire) {
this.refreshToken = refreshToken;
this.expire = expire;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public String getRefreshToken() {
return refreshToken;
}
@Override
public long getExpire() {
return expire;
}
}
}

View file

@ -3,6 +3,11 @@
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.profiles.PlayerProfile;
import pro.gravit.launcher.core.api.model.SelfUser;
import pro.gravit.launcher.core.api.model.Texture;
import java.util.Map;
import java.util.UUID;
public class CurrentUserRequestEvent extends RequestEvent {
public final UserInfo userInfo;
@ -16,9 +21,40 @@ public String getType() {
return "currentUser";
}
public static class UserInfo {
public static class UserInfo implements SelfUser {
public ClientPermissions permissions;
public String accessToken;
public PlayerProfile playerProfile;
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public String getUsername() {
return playerProfile.getUsername();
}
@Override
public UUID getUUID() {
return playerProfile.getUUID();
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Texture> getAssets() {
return (Map) playerProfile.getAssets();
}
@Override
public Map<String, String> getProperties() {
return playerProfile.getProperties();
}
}
}

View file

@ -1,9 +1,16 @@
package pro.gravit.launcher.base.events.request;
import pro.gravit.launcher.base.request.auth.details.AuthLoginOnlyDetails;
import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails;
import pro.gravit.launcher.core.LauncherNetworkAPI;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethod;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
import pro.gravit.launcher.core.api.method.details.AuthPasswordDetails;
import pro.gravit.launcher.core.api.method.details.AuthWebDetails;
import pro.gravit.utils.TypeSerializeInterface;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@ -37,10 +44,11 @@ public enum ServerFeature {
}
}
public interface AuthAvailabilityDetails extends TypeSerializeInterface {
public interface AuthAvailabilityDetails extends AuthMethodDetails, TypeSerializeInterface {
AuthMethodDetails toAuthMethodDetails();
}
public static class AuthAvailability {
public static class AuthAvailability implements AuthMethod {
public final List<AuthAvailabilityDetails> details;
@LauncherNetworkAPI
public String name;
@ -59,5 +67,34 @@ public AuthAvailability(List<AuthAvailabilityDetails> details, String name, Stri
this.visible = visible;
this.features = features;
}
@Override
public List<AuthMethodDetails> getDetails() {
List<AuthMethodDetails> convert = new ArrayList<>();
for(var e : details) {
convert.add(e.toAuthMethodDetails());
}
return convert;
}
@Override
public String getName() {
return name;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public boolean isVisible() {
return visible;
}
@Override
public Set<String> getFeatures() {
return features;
}
}
}

View file

@ -6,6 +6,7 @@
import pro.gravit.launcher.base.profiles.optional.OptionalDepend;
import pro.gravit.launcher.base.profiles.optional.OptionalFile;
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.VerifyHelper;
import pro.gravit.utils.launch.LaunchOptions;
@ -15,7 +16,7 @@
import java.nio.file.Path;
import java.util.*;
public final class ClientProfile implements Comparable<ClientProfile> {
public final class ClientProfile implements Comparable<ClientProfile>, ProfileFeatureAPI.ClientProfile {
private static final FileNameMatcher ASSET_MATCHER = new FileNameMatcher(
new String[0], new String[]{"indexes", "objects"}, new String[0]);
private transient Path profileFilePath;

View file

@ -1,5 +1,6 @@
package pro.gravit.launcher.base.profiles;
import pro.gravit.launcher.core.api.model.User;
import pro.gravit.utils.helper.IOHelper;
import java.util.HashMap;
@ -7,7 +8,7 @@
import java.util.Objects;
import java.util.UUID;
public final class PlayerProfile {
public final class PlayerProfile implements User {
public final UUID uuid;
public final String username;
@ -49,4 +50,23 @@ public static UUID offlineUUID(String username) {
return UUID.nameUUIDFromBytes(IOHelper.encodeASCII("OfflinePlayer:" + username));
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public Map<String, Texture> getAssets() {
return assets;
}
@Override
public Map<String, String> getProperties() {
return properties;
}
}

View file

@ -14,7 +14,7 @@
import java.util.Arrays;
import java.util.Map;
public final class Texture extends StreamObject {
public final class Texture extends StreamObject implements pro.gravit.launcher.core.api.model.Texture {
private static final SecurityHelper.DigestAlgorithm DIGEST_ALGO = SecurityHelper.DigestAlgorithm.SHA256;
// Instance
@ -85,4 +85,19 @@ public String toString() {
", metadata=" + metadata +
'}';
}
@Override
public String getUrl() {
return url;
}
@Override
public String getHash() {
return SecurityHelper.toHex(digest);
}
@Override
public Map<String, String> getMetadata() {
return metadata;
}
}

View file

@ -0,0 +1,31 @@
package pro.gravit.launcher.base.request;
import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launcher.base.request.update.LauncherRequest;
import pro.gravit.launcher.core.api.features.CoreFeatureAPI;
import pro.gravit.launcher.core.api.method.AuthMethod;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class RequestCoreFeatureAPIImpl implements CoreFeatureAPI {
private final RequestService request;
public RequestCoreFeatureAPIImpl(RequestService request) {
this.request = request;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public CompletableFuture<List<AuthMethod>> getAuthMethods() {
return request.request(new GetAvailabilityAuthRequest()).thenApply(response -> (List) response.list);
}
@Override
public CompletableFuture<LauncherUpdateInfo> checkUpdates() {
return request.request(new LauncherRequest()).thenApply(response -> new LauncherUpdateInfo(response.url,
"Unknown", response.needUpdate, response.needUpdate));
}
}

View file

@ -0,0 +1,169 @@
package pro.gravit.launcher.base.request;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.request.auth.*;
import pro.gravit.launcher.base.request.auth.password.*;
import pro.gravit.launcher.base.request.update.ProfilesRequest;
import pro.gravit.launcher.base.request.update.UpdateRequest;
import pro.gravit.launcher.base.request.uuid.ProfileByUUIDRequest;
import pro.gravit.launcher.base.request.uuid.ProfileByUsernameRequest;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.launcher.core.api.method.password.AuthChainPassword;
import pro.gravit.launcher.core.api.method.password.AuthOAuthPassword;
import pro.gravit.launcher.core.api.method.password.AuthPlainPassword;
import pro.gravit.launcher.core.api.method.password.AuthTotpPassword;
import pro.gravit.launcher.core.api.model.SelfUser;
import pro.gravit.launcher.core.api.model.User;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.utils.helper.SecurityHelper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class RequestFeatureAPIImpl implements AuthFeatureAPI, UserFeatureAPI, ProfileFeatureAPI {
private final RequestService request;
private final String authId;
public RequestFeatureAPIImpl(RequestService request, String authId) {
this.request = request;
this.authId = authId;
}
@Override
public CompletableFuture<SelfUser> getCurrentUser() {
return request.request(new CurrentUserRequest()).thenApply(response -> response.userInfo);
}
@Override
public CompletableFuture<AuthResponse> auth(String login, AuthMethodPassword password) {
return request.request(new AuthRequest(login, convertAuthPasswordAll(password), authId, false, AuthRequest.ConnectTypes.CLIENT))
.thenApply(response -> new AuthResponse(response.makeUserInfo(), response.oauth));
}
private AuthRequest.AuthPasswordInterface convertAuthPasswordAll(AuthMethodPassword password) {
AuthRequest.AuthPasswordInterface requestPassword;
if(password instanceof AuthChainPassword chain) {
if(chain.list().size() == 1) {
requestPassword = convertAuthPassword(chain.list().get(0));
} else if(chain.list().size() == 2) {
requestPassword = new Auth2FAPassword(convertAuthPassword(chain.list().get(0)),
convertAuthPassword(chain.list().get(1)));
} else {
var multi = new AuthMultiPassword();
for(var e : chain.list()) {
multi.list.add(convertAuthPassword(e));
}
requestPassword = multi;
}
} else {
requestPassword = convertAuthPassword(password);
}
return requestPassword;
}
private AuthRequest.AuthPasswordInterface convertAuthPassword(AuthMethodPassword password) {
if(password instanceof AuthPlainPassword plain) {
String encryptKey = Launcher.getConfig().passwordEncryptKey;
if(encryptKey != null) {
try {
return new AuthAESPassword(SecurityHelper.encrypt(encryptKey, plain.value()));
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return new pro.gravit.launcher.base.request.auth.password.AuthPlainPassword(plain.value());
}
} else if(password instanceof AuthTotpPassword totp) {
return new AuthTOTPPassword(totp.value());
} else if(password instanceof AuthOAuthPassword oauth) {
return new AuthCodePassword(oauth.redirectUrl());
} else if(password instanceof AuthRequest.AuthPasswordInterface custom) {
return custom;
}
else {
throw new UnsupportedOperationException();
}
}
@Override
public CompletableFuture<User> getUserByUsername(String username) {
return request.request(new ProfileByUsernameRequest(username)).thenApply(response -> response.playerProfile);
}
@Override
public CompletableFuture<User> getUserByUUID(UUID uuid) {
return request.request(new ProfileByUUIDRequest(uuid)).thenApply(response -> response.playerProfile);
}
@Override
public CompletableFuture<Void> joinServer(String username, String accessToken, String serverID) {
return request.request(new JoinServerRequest(username, accessToken, serverID)).thenCompose(response -> {
if(response.allow) {
return CompletableFuture.completedFuture(null);
} else {
return CompletableFuture.failedFuture(new RequestException("Not allowed"));
}
});
}
@Override
public CompletableFuture<Void> joinServer(UUID uuid, String accessToken, String serverID) {
return request.request(new JoinServerRequest(uuid, accessToken, serverID)).thenCompose(response -> {
if(response.allow) {
return CompletableFuture.completedFuture(null);
} else {
return CompletableFuture.failedFuture(new RequestException("Not allowed"));
}
});
}
@Override
public CompletableFuture<CheckServerResponse> checkServer(String username, String serverID, boolean extended) {
return request.request(new CheckServerRequest(username, serverID, extended, extended))
.thenApply(response -> new CheckServerResponse(response.playerProfile, response.hardwareId,
response.sessionId, response.sessionProperties));
}
@Override
public CompletableFuture<AuthToken> refreshToken(String refreshToken) {
return request.request(new RefreshTokenRequest(authId, refreshToken)).thenApply(response -> response.oauth);
}
@Override
public CompletableFuture<SelfUser> restore(String accessToken, boolean fetchUser) {
Map<String, String> extended = new HashMap<>();
if(Request.getExtendedTokens() != null) { // TODO: Control extended token
for(var e : Request.getExtendedTokens().entrySet()) {
extended.put(e.getKey(), e.getValue().token);
}
}
return request.request(new RestoreRequest(authId, accessToken, extended, fetchUser)).thenApply(e -> {
// TODO: invalidToken process
return e.userInfo;
});
}
@Override
public CompletableFuture<Void> exit() {
return request.request(new ExitRequest()).thenApply(response -> null);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> getProfiles() {
return request.request(new ProfilesRequest()).thenApply(response -> (List) response.profiles);
}
@Override
public CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName) {
return request.request(new UpdateRequest(dirName)).thenApply(response -> new UpdateInfoData(response.hdir, response.url));
}
public record UpdateInfoData(HashedDir hdir, String url) implements ProfileFeatureAPI.UpdateInfo {}
}

View file

@ -5,7 +5,7 @@
import java.util.concurrent.ExecutionException;
public interface RequestService {
<T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException;
<T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request);
void open() throws Exception;
void registerEventHandler(EventHandler handler);

View file

@ -5,6 +5,7 @@
import pro.gravit.launcher.base.request.Request;
import pro.gravit.launcher.base.request.auth.password.*;
import pro.gravit.launcher.base.request.websockets.WebSocketRequest;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.utils.ProviderMap;
public final class AuthRequest extends Request<AuthRequestEvent> implements WebSocketRequest {
@ -64,7 +65,7 @@ public enum ConnectTypes {
API
}
public interface AuthPasswordInterface {
public interface AuthPasswordInterface extends AuthMethodPassword {
boolean check();
default boolean isAllowSave() {

View file

@ -1,10 +1,16 @@
package pro.gravit.launcher.base.request.auth.details;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthLoginOnlyDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails {
@Override
public String getType() {
return "loginonly";
}
@Override
public AuthMethodDetails toAuthMethodDetails() {
return new pro.gravit.launcher.core.api.method.details.AuthLoginOnlyDetails();
}
}

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.base.request.auth.details;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthPasswordDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails {
@Override
@ -9,4 +10,8 @@ public String getType() {
}
@Override
public AuthMethodDetails toAuthMethodDetails() {
return new pro.gravit.launcher.core.api.method.details.AuthPasswordDetails();
}
}

View file

@ -1,6 +1,7 @@
package pro.gravit.launcher.base.request.auth.details;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthTotpDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails {
public final String alg;
@ -20,4 +21,9 @@ public AuthTotpDetails(String alg) {
public String getType() {
return "totp";
}
@Override
public AuthMethodDetails toAuthMethodDetails() {
return new pro.gravit.launcher.core.api.method.details.AuthTotpDetails(maxKeyLength);
}
}

View file

@ -1,6 +1,8 @@
package pro.gravit.launcher.base.request.auth.details;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
import pro.gravit.launcher.core.api.method.details.AuthWebDetails;
public class AuthWebViewDetails implements GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails {
public final String url;
@ -26,4 +28,9 @@ public AuthWebViewDetails(String url, String redirectUrl) {
public String getType() {
return "webview";
}
@Override
public AuthMethodDetails toAuthMethodDetails() {
return new AuthWebDetails(url, redirectUrl, canBrowser);
}
}

View file

@ -6,6 +6,14 @@ public class Auth2FAPassword implements AuthRequest.AuthPasswordInterface {
public AuthRequest.AuthPasswordInterface firstPassword;
public AuthRequest.AuthPasswordInterface secondPassword;
public Auth2FAPassword() {
}
public Auth2FAPassword(AuthRequest.AuthPasswordInterface firstPassword, AuthRequest.AuthPasswordInterface secondPassword) {
this.firstPassword = firstPassword;
this.secondPassword = secondPassword;
}
@Override
public boolean check() {
return firstPassword != null && firstPassword.check() && secondPassword != null && secondPassword.check();

View file

@ -5,6 +5,13 @@
public class AuthTOTPPassword implements AuthRequest.AuthPasswordInterface {
public String totp;
public AuthTOTPPassword() {
}
public AuthTOTPPassword(String totp) {
this.totp = totp;
}
@Override
public boolean check() {
return true;

View file

@ -98,10 +98,14 @@ public <T extends WebSocketEvent> void eventHandle(T webSocketEvent) {
processEventHandlers(webSocketEvent);
}
public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException {
public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) {
CompletableFuture<T> result = new CompletableFuture<>();
futureMap.put(request.requestUUID, result);
sendObject(request, WebSocketRequest.class);
try {
sendObject(request, WebSocketRequest.class);
} catch (IOException e) {
return CompletableFuture.failedFuture(e);
}
return result;
}

View file

@ -0,0 +1,109 @@
package pro.gravit.launcher.core;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.method.AuthMethod;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.launcher.core.api.model.SelfUser;
import pro.gravit.launcher.core.api.model.Texture;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface LauncherBackendAPI {
void setCallback(MainCallback callback);
CompletableFuture<LauncherInitData> init();
void selectAuthMethod(AuthMethod method);
CompletableFuture<SelfUser> tryAuthorize();
CompletableFuture<SelfUser> authorize(String login, AuthMethodPassword password);
CompletableFuture<List<ProfileFeatureAPI.ClientProfile>> fetchProfiles();
ClientProfileSettings makeClientProfileSettings(ProfileFeatureAPI.ClientProfile profile);
CompletableFuture<ReadyProfile> downloadProfile(ProfileFeatureAPI.ClientProfile profile, ClientProfileSettings settings, DownloadCallback callback);
// Tools
CompletableFuture<byte[]> fetchTexture(Texture texture);
record LauncherInitData(List<AuthMethod> methods) {}
interface ReadyProfile {
ProfileFeatureAPI.ClientProfile getClientProfile();
ClientProfileSettings getSettings();
void run(RunCallback callback) throws Exception;
}
interface ClientProfileSettings {
long getReservedMemoryBytes();
long getMaxMemoryBytes();
void setReservedMemoryBytes(long value);
List<Flag> getFlags();
boolean hasFlag(Flag flag);
void addFlag(Flag flag);
void removeFlag(Flag flag);
List<ProfileFeatureAPI.OptionalMod> getEnabledOptionals();
void enableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
void disableOptional(ProfileFeatureAPI.OptionalMod mod, ChangedOptionalStatusCallback callback);
enum Flag {
}
interface ChangedOptionalStatusCallback {
void onChanged(ProfileFeatureAPI.OptionalMod mod, boolean enabled);
}
}
// Callbacks
class MainCallback {
// On any request
public void onChangeStatus(String status) {
}
public void onProfiles(List<ProfileFeatureAPI.ClientProfile> profiles) {
}
public void onAuthorize(SelfUser selfUser) {
}
public void onNotify(String header, String description) {
}
}
class RunCallback {
public void onStarted() {
}
public void onFinished(int code) {
}
public void onNormalOutput(byte[] buf, int offset, int size) {
}
public void onErrorOutput(byte[] buf, int offset, int size) {
}
}
class DownloadCallback {
public void onStage(String stage) {
}
public void onCanCancel(Runnable cancel) {
}
public void onTotalDownload(long total) {
}
public void onCurrentDownloaded(long current) {
}
}
}

View file

@ -0,0 +1,34 @@
package pro.gravit.launcher.core.api;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.FeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
import java.util.HashMap;
import java.util.Map;
public class LauncherAPI {
private final Map<Class<? extends FeatureAPI>, FeatureAPI> map;
public LauncherAPI(Map<Class<? extends FeatureAPI>, FeatureAPI> map) {
this.map = new HashMap<>(map);
}
public AuthFeatureAPI auth() {
return get(AuthFeatureAPI.class);
}
public UserFeatureAPI user() {
return get(UserFeatureAPI.class);
}
public ProfileFeatureAPI profile() {
return get(ProfileFeatureAPI.class);
}
@SuppressWarnings("unchecked")
public<T extends FeatureAPI> T get(Class<T> clazz) {
return (T) map.get(clazz);
}
}

View file

@ -0,0 +1,70 @@
package pro.gravit.launcher.core.api;
import pro.gravit.launcher.core.api.features.AuthFeatureAPI;
import pro.gravit.launcher.core.api.features.CoreFeatureAPI;
import pro.gravit.launcher.core.api.features.ProfileFeatureAPI;
import pro.gravit.launcher.core.api.features.UserFeatureAPI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
public final class LauncherAPIHolder {
private static volatile CoreFeatureAPI coreAPI;
private static volatile LauncherAPI api;
private static volatile Function<String, LauncherAPI> createApiFactory;
private static final Map<String, LauncherAPI> map = new ConcurrentHashMap<>();
public static void setCoreAPI(CoreFeatureAPI coreAPI) {
LauncherAPIHolder.coreAPI = coreAPI;
}
public static void setApi(LauncherAPI api) {
LauncherAPIHolder.api = api;
}
public static void setCreateApiFactory(Function<String, LauncherAPI> createApiFactory) {
LauncherAPIHolder.createApiFactory = createApiFactory;
}
public static void changeAuthId(String authId) {
LauncherAPIHolder.api = map.computeIfAbsent(authId, createApiFactory);
}
public static LauncherAPI get() {
return api;
}
public static LauncherAPI get(String authId) {
return map.computeIfAbsent(authId, createApiFactory);
}
public static CoreFeatureAPI core() {
return coreAPI;
}
public static AuthFeatureAPI auth() {
if(api == null) {
throw new UnsupportedOperationException();
}
return api.auth();
}
public static UserFeatureAPI user() {
if(api == null) {
throw new UnsupportedOperationException();
}
return api.user();
}
public static ProfileFeatureAPI profile() {
if(api == null) {
throw new UnsupportedOperationException();
}
return api.profile();
}
public static void set(LauncherAPI api) {
LauncherAPIHolder.api = api;
}
}

View file

@ -0,0 +1,24 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.api.method.AuthMethod;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import pro.gravit.launcher.core.api.model.SelfUser;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface AuthFeatureAPI extends FeatureAPI {
CompletableFuture<SelfUser> getCurrentUser();
CompletableFuture<AuthResponse> auth(String login, AuthMethodPassword password);
CompletableFuture<AuthToken> refreshToken(String refreshToken);
CompletableFuture<SelfUser> restore(String accessToken, boolean fetchUser);
CompletableFuture<Void> exit();
record AuthResponse(SelfUser user, AuthToken authToken) {}
interface AuthToken {
String getAccessToken();
String getRefreshToken();
long getExpire();
}
}

View file

@ -0,0 +1,13 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.api.method.AuthMethod;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface CoreFeatureAPI {
CompletableFuture<List<AuthMethod>> getAuthMethods();
CompletableFuture<LauncherUpdateInfo> checkUpdates();
record LauncherUpdateInfo(String url, String version, boolean available, boolean required) {}
}

View file

@ -0,0 +1,4 @@
package pro.gravit.launcher.core.api.features;
public interface FeatureAPI {
}

View file

@ -0,0 +1,26 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.hasher.HashedDir;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface ProfileFeatureAPI extends FeatureAPI {
CompletableFuture<List<ClientProfile>> getProfiles();
CompletableFuture<UpdateInfo> fetchUpdateInfo(String dirName);
interface UpdateInfo {}
interface ClientProfile {
String getName();
UUID getUUID();
List<OptionalMod> getOptionalMods();
}
interface OptionalMod {
String getName();
String getDescription();
String getCategory();
}
}

View file

@ -0,0 +1,37 @@
package pro.gravit.launcher.core.api.features;
import pro.gravit.launcher.core.api.model.User;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public interface UserFeatureAPI extends FeatureAPI {
CompletableFuture<User> getUserByUsername(String username);
CompletableFuture<User> getUserByUUID(UUID uuid);
CompletableFuture<Void> joinServer(String username, String accessToken, String serverID);
CompletableFuture<Void> joinServer(UUID uuid, String accessToken, String serverID);
CompletableFuture<CheckServerResponse> checkServer(String username, String serverID, boolean extended);
default CompletableFuture<List<User>> getUsersByUsernames(List<String> usernames) {
List<CompletableFuture<User>> list = new ArrayList<>();
for(var username : usernames) {
list.add(getUserByUsername(username));
}
return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)).thenApply(x -> {
List<User> r = new ArrayList<>();
for(var e : list) {
try {
r.add(e.get());
} catch (InterruptedException | ExecutionException ex) {
r.add(null);
}
}
return r;
});
}
record CheckServerResponse(User user, String hardwareId, String sessionId, Map<String, String> sessionProperties) {}
}

View file

@ -0,0 +1,12 @@
package pro.gravit.launcher.core.api.method;
import java.util.List;
import java.util.Set;
public interface AuthMethod {
List<AuthMethodDetails> getDetails();
String getName();
String getDisplayName();
boolean isVisible();
Set<String> getFeatures();
}

View file

@ -0,0 +1,4 @@
package pro.gravit.launcher.core.api.method;
public interface AuthMethodDetails {
}

View file

@ -0,0 +1,4 @@
package pro.gravit.launcher.core.api.method;
public interface AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.details;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthLoginOnlyDetails implements AuthMethodDetails {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.details;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public class AuthPasswordDetails implements AuthMethodDetails {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.details;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public record AuthTotpDetails(int length) implements AuthMethodDetails {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.details;
import pro.gravit.launcher.core.api.method.AuthMethodDetails;
public record AuthWebDetails(String url, String redirectUrl, boolean externalBrowserSupport) implements AuthMethodDetails {
}

View file

@ -0,0 +1,8 @@
package pro.gravit.launcher.core.api.method.password;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
import java.util.List;
public record AuthChainPassword(List<AuthMethodPassword> list) implements AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.password;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
public record AuthOAuthPassword(String redirectUrl) implements AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.password;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
public record AuthPlainPassword(String value) implements AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.method.password;
import pro.gravit.launcher.core.api.method.AuthMethodPassword;
public record AuthTotpPassword(String value) implements AuthMethodPassword {
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.model;
public interface SelfUser extends User {
String getAccessToken();
UserPermissions getPermissions();
}

View file

@ -0,0 +1,9 @@
package pro.gravit.launcher.core.api.model;
import java.util.Map;
public interface Texture {
String getUrl();
String getHash();
Map<String, String> getMetadata();
}

View file

@ -0,0 +1,11 @@
package pro.gravit.launcher.core.api.model;
import java.util.Map;
import java.util.UUID;
public interface User {
String getUsername();
UUID getUUID();
Map<String, Texture> getAssets();
Map<String, String> getProperties();
}

View file

@ -0,0 +1,6 @@
package pro.gravit.launcher.core.api.model;
public interface UserPermissions {
boolean hasRole(String role);
boolean hasPerm(String action);
}

View file

@ -5,10 +5,10 @@
public final class Version implements Comparable<Version> {
public static final int MAJOR = 5;
public static final int MINOR = 6;
public static final int PATCH = 3;
public static final int MINOR = 7;
public static final int PATCH = 0;
public static final int BUILD = 1;
public static final Version.Type RELEASE = Type.DEV;
public static final Version.Type RELEASE = Type.EXPERIMENTAL;
public final int major;
public final int minor;
public final int patch;

View file

@ -29,10 +29,13 @@
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Type;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ServerWrapper extends JsonConfigurable<ServerWrapper.Config> {
public static final Path configFile = Paths.get(System.getProperty("serverwrapper.configFile", "ServerWrapperConfig.json"));
@ -187,14 +190,30 @@ public void run(String... args) throws Throwable {
System.load(Paths.get(config.nativesDir).resolve(ClientService.findLibrary(e)).toAbsolutePath().toString());
}
}
List<String> classpath = config.classpath;
if(config.resolveClassPath) {
classpath = new ArrayList<>();
for(var cp : config.classpath) {
Path p = Paths.get(cp);
if(Files.isDirectory(p)) {
try(Stream<Path> files = Files.walk(p, FileVisitOption.FOLLOW_LINKS)) {
var resolved = files.filter(e -> !Files.isDirectory(e))
.filter(e -> e.getFileName().toString().endsWith(".jar")).map(e -> e.toAbsolutePath().toString()).toList();
classpath.addAll(resolved);
}
} else {
classpath.add(p.toAbsolutePath().toString());
}
}
}
switch (config.classLoaderConfig) {
case LAUNCHER:
launch = new LegacyLaunch();
System.setProperty("java.class.path", String.join(File.pathSeparator, config.classpath));
System.setProperty("java.class.path", String.join(File.pathSeparator, classpath));
break;
case MODULE:
launch = new ModuleLaunch();
System.setProperty("java.class.path", String.join(File.pathSeparator, config.classpath));
System.setProperty("java.class.path", String.join(File.pathSeparator, classpath));
break;
default:
if(ServerAgent.isAgentStarted()) {
@ -207,7 +226,7 @@ public void run(String... args) throws Throwable {
LaunchOptions options = new LaunchOptions();
options.enableHacks = config.enableHacks;
options.moduleConf = config.moduleConf;
classLoaderControl = launch.init(config.classpath.stream().map(Paths::get).collect(Collectors.toCollection(ArrayList::new)), config.nativesDir, options);
classLoaderControl = launch.init(classpath.stream().map(Paths::get).collect(Collectors.toCollection(ArrayList::new)), config.nativesDir, options);
if(ServerAgent.isAgentStarted()) {
ClientService.instrumentation = ServerAgent.inst;
}
@ -269,6 +288,7 @@ public static final class Config {
public String address;
public String serverName;
public boolean autoloadLibraries;
public boolean resolveClassPath = true;
public String logFile;
public List<String> classpath;
public ClientProfile.ClassLoaderConfig classLoaderConfig;