Skip to content

Commit

Permalink
feat(skymp5-client): make server settings more flexible (#2347)
Browse files Browse the repository at this point in the history
  • Loading branch information
nic11 authored Feb 19, 2025
1 parent 247fc71 commit 8c58a67
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 82 deletions.
2 changes: 2 additions & 0 deletions skymp5-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { WorldView } from "./view/worldView";
import { KeyboardEventsService } from "./services/services/keyboardEventsService";
import { MagicSyncService } from "./services/services/magicSyncService";
import { ProfilingService } from "./services/services/profilingService";
import { SettingsService } from "./services/services/settingsService";

once("update", () => {
Utility.setINIBool("bAlwaysActive:General", true);
Expand Down Expand Up @@ -82,6 +83,7 @@ const main = () => {
new NetworkingService(sp, controller),
new RemoteServer(sp, controller),
new SpSnippetService(sp, controller),
new SettingsService(sp, controller),
new SweetTaffyDynamicPerksService(sp, controller),
new SweetTaffyStaticPerksService(sp, controller),
new SweetTaffySweetCantDropService(sp, controller),
Expand Down
48 changes: 12 additions & 36 deletions skymp5-client/src/services/services/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { NetworkingService } from "./networkingService";
import { CustomPacketMessage2 } from "../messages/customPacketMessage2";
import { MsgType } from "../../messages";
import { ConnectionDenied } from "../events/connectionDenied";
import { SettingsService } from "./settingsService";

// for browsersideWidgetSetter
declare const window: any;
Expand Down Expand Up @@ -42,6 +43,7 @@ let authData: RemoteAuthGameData | null = null;
export class AuthService extends ClientListener {
constructor(private sp: Sp, private controller: CombinedController) {
super();

this.controller.emitter.on("authNeeded", (e) => this.onAuthNeeded(e));
this.controller.emitter.on("browserWindowLoaded", (e) => this.onBrowserWindowLoaded(e));
this.controller.emitter.on("createActorMessage", (e) => this.onCreateActorMessage(e));
Expand All @@ -53,27 +55,6 @@ export class AuthService extends ClientListener {
this.controller.once("update", () => this.onceUpdate());
}

// TODO: consider moving to a separate service called SettingsService
public getServerMasterKey() {
let masterKey = this.sp.settings["skymp5-client"]["server-master-key"];
if (!masterKey) {
masterKey = this.sp.settings["skymp5-client"]["master-key"];
}
if (!masterKey) {
masterKey = this.sp.settings["skymp5-client"]["server-ip"] + ":" + this.sp.settings["skymp5-client"]["server-port"];
}
return masterKey;
}

// TODO: consider moving to a separate service called SettingsService
public getMasterUrl() {
return this.normalizeUrl((this.sp.settings["skymp5-client"]["master"] as string) || "https://gateway.skymp.net");
}

public getMasterApiId() {
return authData?.masterApiId;
}

private onAuthNeeded(e: AuthNeededEvent) {
logTrace(this, `Received authNeeded event`);

Expand Down Expand Up @@ -196,14 +177,16 @@ export class AuthService extends ClientListener {
return;
}

const settingsService = this.controller.lookupListener(SettingsService);

logTrace(this, `onBrowserMessage:`, JSON.stringify(e.arguments));

const eventKey = e.arguments[0];
switch (eventKey) {
case events.openDiscordOauth:
browserState.comment = 'открываем браузер...';
this.refreshWidgets();
this.sp.win32.loadUrl(`${this.getMasterUrl()}/api/users/login-discord?state=${this.discordAuthState}`);
this.sp.win32.loadUrl(`${settingsService.getMasterUrl()}/api/users/login-discord?state=${this.discordAuthState}`);
break;
case events.authAttempt:
if (authData === null) {
Expand Down Expand Up @@ -243,10 +226,10 @@ export class AuthService extends ClientListener {
}

private createPlaySession(token: string, callback: (res: string, err: string) => void) {
const client = new this.sp.HttpClient(this.getMasterUrl());

const route = `/api/users/me/play/${this.getServerMasterKey()}`;
const settingsService = this.controller.lookupListener(SettingsService);
const client = new this.sp.HttpClient(settingsService.getMasterUrl());

const route = `/api/users/me/play/${settingsService.getServerMasterKey()}`;
logTrace(this, `Creating play session ${route}`);

client.post(route, {
Expand All @@ -259,8 +242,7 @@ export class AuthService extends ClientListener {
}, (res) => {
if (res.status != 200) {
callback('', 'status code ' + res.status);
}
else {
} else {
// TODO: handle JSON.parse failure?
callback(JSON.parse(res.body).session, '');
}
Expand All @@ -273,9 +255,10 @@ export class AuthService extends ClientListener {
return;
}

const settingsService = this.controller.lookupListener(SettingsService);
const timersService = this.controller.lookupListener(TimersService);

new this.sp.HttpClient(this.getMasterUrl())
new this.sp.HttpClient(settingsService.getMasterUrl())
.get("/api/users/login-discord/status?state=" + this.discordAuthState, undefined,
// @ts-ignore
(response) => {
Expand Down Expand Up @@ -334,7 +317,7 @@ export class AuthService extends ClientListener {
this.authDialogOpen = true;
};

private readAuthDataFromDisk(): RemoteAuthGameData | null {
public readAuthDataFromDisk(): RemoteAuthGameData | null {
logTrace(this, `Reading`, this.pluginAuthDataName, `from disk`);

try {
Expand Down Expand Up @@ -371,13 +354,6 @@ export class AuthService extends ClientListener {
}
};

private normalizeUrl(url: string) {
if (url.endsWith('/')) {
return url.slice(0, url.length - 1);
}
return url;
};

private deniedWidgetSetter = () => {
const widget = {
type: "form",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Game, Utility, HttpClient, printConsole, createText } from "skyrimPlatform";
import { getScreenResolution } from "../../view/formView";
import { ClientListener, CombinedController, Sp } from "./clientListener";
import { Mod, ServerManifest } from "../messages_http/serverManifest";
import { Mod } from "../messages_http/serverManifest";
import { logTrace } from "../../logging";
import { AuthService } from "./authService";
import { SettingsService } from "./settingsService";

const STATE_KEY = 'loadOrderCheckState';

Expand All @@ -22,10 +22,12 @@ export class LoadOrderVerificationService extends ClientListener {
}

private verifyLoadOrder() {
const settingsService = this.controller.lookupListener(SettingsService);

this.resetText();
const clientMods = this.getClientMods();
this.printModOrder('Client load order:', clientMods);
return this.getServerMods(5)
return settingsService.getServerMods()
.then((serverMods) => {
this.printModOrder('Server load order:', serverMods);
if (clientMods.length < serverMods.length) {
Expand Down Expand Up @@ -106,37 +108,6 @@ export class LoadOrderVerificationService extends ClientListener {
}
}

private getServerMods(retriesLeft: number): Promise<Mod[]> {
const authService = this.controller.lookupListener(AuthService);

const addr = authService.getMasterUrl();
const masterKey = authService.getServerMasterKey();
printConsole(addr);
printConsole(masterKey);

return new HttpClient(addr)
.get(`/api/servers/${masterKey}/manifest.json`)
.then((res) => {
if (res.status != 200) {
throw new Error(`Status code ${res.status}, error ${res.error}`);
}
const manifest = JSON.parse(res.body) as ServerManifest;
if (manifest.versionMajor !== 1) {
throw new Error(`Server manifest version is ${manifest.versionMajor}, we expect 1`);
}
return manifest.mods;
})
.catch((err) => {
printConsole("Can't get server mods", err);
if (retriesLeft > 0) {
printConsole(`${retriesLeft} retries left...`);
return Utility.wait(0.1 + Math.random())
.then(() => this.getServerMods(retriesLeft - 1));
}
return [];
});
};

private enumerateClientMods(getCount: (() => number), getAt: ((idx: number) => string)) {
const result = [];
for (let i = 0; i < getCount(); ++i) {
Expand Down
146 changes: 146 additions & 0 deletions skymp5-client/src/services/services/settingsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { HttpClient, HttpHeaders, HttpResponse, printConsole, Utility } from "skyrimPlatform";
import { AuthService } from "./authService";
import { ClientListener, CombinedController, Sp } from "./clientListener";
import { Mod, ServerManifest } from "../messages_http/serverManifest";
import { TimersService } from "./timersService";

interface IHttpClientWithCallback {
get(path: string, options?: { headers?: HttpHeaders }): Promise<HttpResponse>;
post(path: string, options: { body: string, contentType: string, headers?: HttpHeaders }): Promise<HttpResponse>;

get(path: string, options: { headers?: HttpHeaders } | undefined, callback: (response: HttpResponse) => void): void;
}

export interface TargetPeer {
host: string;
port: number;
}

export class SettingsService extends ClientListener {
constructor(private sp: Sp, private controller: CombinedController) {
super();
}

public getServerMasterKey() {
let masterKey = this.sp.settings["skymp5-client"]["server-master-key"];
if (!masterKey) {
masterKey = this.sp.settings["skymp5-client"]["master-key"];
}
if (!masterKey) {
masterKey = this.sp.settings["skymp5-client"]["server-ip"] + ":" + this.sp.settings["skymp5-client"]["server-port"];
}
return masterKey;
}

public getMasterUrl() {
return this.normalizeUrl((this.sp.settings["skymp5-client"]["master"] as string) || "https://gateway.skymp.net");
}

public makeMasterApiClient(): IHttpClientWithCallback {
const masterApiBaseUrl = this.getMasterUrl();
return new HttpClient(masterApiBaseUrl) as IHttpClientWithCallback;
}

// have to use callbacks here: promises don't work in the main menu
public getTargetPeer(callback: (targetPeer: TargetPeer) => void) {
const masterApiClient = this.makeMasterApiClient();
const masterKey = this.getServerMasterKey();

const serverInfoRequestTimeoutMs = 5000;
const defaultPeer: TargetPeer = {
host: this.sp.settings['skymp5-client']['server-ip'] as string,
port: this.sp.settings['skymp5-client']['server-port'] as number,
};

let resolved = false;

const states = {
start: () => {
try {
this.controller.lookupListener(TimersService).setTimeout(
() => states.reject(new Error('getTargetPeer: serverinfo request timed out')),
serverInfoRequestTimeoutMs,
);

let headers: HttpHeaders = {};
let session = this.controller.lookupListener(AuthService).readAuthDataFromDisk()?.session;
if (session) {
headers['X-Session'] = session;
}

masterApiClient.get(
`/api/servers/${masterKey}/serverinfo`, { headers },
states.handleResponse,
);
} catch (e) {
states.reject(e);
}
},
handleResponse: (res: HttpResponse) => {
try {
if (res.status !== 200) {
throw new Error(`status ${res.status}`);
}
states.resolve(JSON.parse(res.body));
} catch (e) {
states.reject(e);
}
},

resolve: (targetPeer: TargetPeer) => {
if (resolved) {
return;
}
resolved = true;

callback(targetPeer);
},
reject: (err: unknown) => {
if (resolved) {
return;
}
resolved = true;

printConsole(`Server info request failed, falling back; error: ${err}`);
callback(defaultPeer);
},
};

states.start();
}

public async getServerMods(): Promise<Mod[]> {
const masterApiClient = this.makeMasterApiClient();

const masterKey = this.getServerMasterKey();
printConsole(masterKey);

for (let attempt = 0; attempt < 5; ++attempt) {
try {
printConsole(`Trying to get server mods, attempt ${attempt}`);
const res = await masterApiClient.get(`/api/servers/${masterKey}/manifest.json`);
if (res.status != 200) {
throw new Error(`status code ${res.status}, error ${res.error}`);
}
const manifest = JSON.parse(res.body) as ServerManifest;
if (manifest.versionMajor !== 1) {
printConsole(`server manifest version is ${manifest.versionMajor}, we expect 1`);
return [];
}
return manifest.mods;
} catch (e) {
printConsole(`Request/parse error: ${e}`);
await Utility.wait(0.1 + Math.random());
}
}

return [];
};

private normalizeUrl(url: string) {
if (url.endsWith('/')) {
return url.slice(0, url.length - 1);
}
return url;
};
}
26 changes: 14 additions & 12 deletions skymp5-client/src/services/services/skympClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ import { ConnectionMessage } from '../events/connectionMessage';
import { CreateActorMessage } from '../messages/createActorMessage';
import { AuthAttemptEvent } from '../events/authAttemptEvent';
import { logTrace } from '../../logging';
import { SettingsService, TargetPeer } from './settingsService';

printConsole('Hello Multiplayer!');
printConsole('settings:', settings['skymp5-client']);

const targetIp = settings['skymp5-client']['server-ip'] as string;
const targetPort = settings['skymp5-client']['server-port'] as number;

export class SkympClient extends ClientListener {
constructor(private sp: Sp, private controller: CombinedController) {
super();
Expand Down Expand Up @@ -86,17 +84,21 @@ export class SkympClient extends ClientListener {
this.sp.printConsole('SkympClient ctor');
}

private async establishConnectionConditional() {
private establishConnectionConditional() {
const isConnected = this.controller.lookupListener(networking.NetworkingService).isConnected();

if (!isConnected) {
storage.targetIp = targetIp;
storage.targetPort = targetPort;

logTrace(this, `Connecting to`, storage.targetIp + ':' + storage.targetPort);
this.controller.lookupListener(networking.NetworkingService).connect(storage.targetIp as string, targetPort);
} else {
if (isConnected) {
logTrace(this, 'Reconnect is not required');
return;
}

this.controller.lookupListener(SettingsService).getTargetPeer(
({ host, port }: TargetPeer) => {
storage.targetIp = host;
storage.targetPort = port;

printConsole(`Connecting to ${host}:${port}`);
this.controller.lookupListener(networking.NetworkingService).connect(host, port);
},
);
}
}

0 comments on commit 8c58a67

Please sign in to comment.