chore: cleanup

This commit is contained in:
flx-sta 2024-10-04 15:50:28 -07:00
parent ab6a19168a
commit 0e3e128494
38 changed files with 588 additions and 513 deletions

View File

@ -0,0 +1,17 @@
import type { UserInfo } from "#app/account";
export interface AccountInfoResponse extends UserInfo {}
export interface AccountLoginRequest {
username: string;
password: string;
}
export interface AccountLoginResponse {
token: string;
}
export interface AccountRegisterRequest {
username: string;
password: string;
}

View File

@ -0,0 +1,10 @@
import type { ScoreboardCategory } from "#app/ui/daily-run-scoreboard";
export interface GetDailyRankingsRequest {
category: ScoreboardCategory;
page?: number;
}
export interface GetDailyRankingsPageCountRequest {
category: ScoreboardCategory;
}

View File

@ -0,0 +1,39 @@
export class UpdateSessionSavedataRequest {
slot: number;
trainerId: number;
secretId: number;
clientSessionId: string;
}
/** This is **NOT** similar to {@linkcode ClearSessionSavedataRequest} */
export interface NewClearSessionSavedataRequest {
slot: number;
clientSessionId: string;
}
export interface GetSessionSavedataRequest {
slot: number;
clientSessionId: string;
}
export interface DeleteSessionSavedataRequest {
slot: number;
clientSessionId: string;
}
/** This is **NOT** similar to {@linkcode NewClearSessionSavedataRequest} */
export interface ClearSessionSavedataRequest {
slot: number;
trainerId: number;
clientSessionId: string;
}
/**
* Pokerogue API response for path: `/savedata/session/clear`
*/
export interface ClearSessionSavedataResponse {
/** Contains the error message if any occured */
error?: string;
/** Is `true` if the request was successfully processed */
success?: boolean;
}

View File

@ -0,0 +1,20 @@
import type { SystemSaveData } from "#app/system/game-data";
export interface GetSystemSavedataRequest {
clientSessionId: string;
}
export class UpdateSystemSavedataRequest {
clientSessionId: string;
trainerId?: number;
secretId?: number;
}
export interface VerifySystemSavedataRequest {
clientSessionId: string;
}
export interface VerifySystemSavedataResponse {
valid: boolean;
systemData: SystemSaveData;
}

View File

@ -1,9 +0,0 @@
/**
* Pokerogue API response for path: `/savedata/session/clear`
*/
export interface PokerogueApiClearSessionData {
/** Contains the error message if any occured */
error?: string;
/** Is `true` if the request was successfully processed */
success?: boolean;
}

View File

@ -44,17 +44,14 @@ export function updateUserInfo(): Promise<[boolean, integer]> {
});
return resolve([ true, 200 ]);
}
pokerogueApi.getAccountInfo().then((accountInfoOrStatus) => {
if (typeof accountInfoOrStatus === "number") {
resolve([ false, accountInfoOrStatus ]);
pokerogueApi.account.getInfo().then(([accountInfo, status]) => {
if (!accountInfo) {
resolve([ false, status ]);
return;
} else {
loggedInUser = accountInfoOrStatus;
loggedInUser = accountInfo;
resolve([ true, 200 ]);
}
}).catch(err => {
console.error(err);
resolve([ false, 500 ]);
});
});
}

View File

@ -15,9 +15,9 @@ export interface DailyRunConfig {
export function fetchDailyRunSeed(): Promise<string | null> {
return new Promise<string | null>((resolve, reject) => {
pokerogueApi.getDailySeed().then(dailySeed => {
pokerogueApi.daily.getSeed().then(dailySeed => {
resolve(dailySeed);
}).catch(err => reject(err)); // TODO: does this ever reject with the api class?
});
});
}

View File

@ -178,8 +178,8 @@ export class GameOverPhase extends BattlePhase {
If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */
if (this.victory) {
if (!Utils.isLocal || Utils.isLocalServerConnected) {
pokerogueApi.newclearSession(this.scene.sessionSlotId, clientSessionId)
.then((isNewClear) => doGameOver(!!isNewClear));
pokerogueApi.savedata.session.newclear({ slot: this.scene.sessionSlotId, clientSessionId })
.then((success) => doGameOver(!!success));
} else {
this.scene.gameData.offlineNewClear(this.scene).then(result => {
doGameOver(result);

View File

@ -3,7 +3,7 @@ import { getCookie } from "#app/utils";
type DataType = "json" | "form-urlencoded";
export abstract class Api {
export abstract class ApiBase {
//#region Fields
protected readonly base: string;
@ -71,4 +71,19 @@ export abstract class Api {
return await fetch(this.base + path, config);
}
/**
* Helper to transform data to {@linkcode URLSearchParams}
* Any key with a value of `undefined` will be ignored.
* Any key with a value of `null` will be included.
* @param data the data to transform to {@linkcode URLSearchParams}
* @returns a {@linkcode URLSearchParams} representaton of {@linkcode data}
*/
protected toUrlSearchParams<D extends Record<string, any>>(data: D) {
const arr = Object.entries(data)
.map(([key, value]) => (value !== undefined ? [key, String(value)] : [key, ""]))
.filter(([, value]) => value !== "");
return new URLSearchParams(arr);
}
}

View File

@ -1,4 +0,0 @@
import type { UserInfo } from "#app/account";
import type { BaseApiResponse } from "./BaseApiResponse";
export interface AccountInfoResponse extends BaseApiResponse, UserInfo {}

View File

@ -1,10 +0,0 @@
import type { BaseApiResponse } from "./BaseApiResponse";
export interface AccountLoginRequest {
username: string;
password: string;
}
export interface AccountLoginResponse extends BaseApiResponse {
token: string;
}

View File

@ -1,4 +0,0 @@
export interface AccountRegisterRequest {
username: string;
password: string;
}

View File

@ -1,6 +0,0 @@
export interface BaseApiResponse {
error?: {
code: number;
message: string;
};
}

View File

@ -1,3 +0,0 @@
import type { GameData } from "#app/system/game-data";
export interface ClientSessionResponse extends GameData {}

View File

@ -1,4 +0,0 @@
export interface ErrorResponse {
code: number;
message: string;
}

View File

@ -1,6 +0,0 @@
export class UpdateSessionSavedataRequest {
slot: number;
trainerId: number;
secretId: number;
clientSessionId: string;
}

View File

@ -1,5 +0,0 @@
export class UpdateSystemSavedataRequest {
clientSessionId: string;
trainerId?: number;
secretId?: number;
}

View File

@ -1,6 +0,0 @@
import type { SystemSaveData } from "#app/system/game-data";
export interface VerifySavedataResponse {
valid: boolean;
systemData: SystemSaveData;
}

View File

@ -0,0 +1,101 @@
import type {
AccountInfoResponse,
AccountLoginRequest,
AccountLoginResponse,
AccountRegisterRequest,
} from "#app/@types/PokerogueAccountApi";
import { SESSION_ID_COOKIE_NAME } from "#app/constants";
import { ApiBase } from "#app/plugins/api/api-base";
import { removeCookie, setCookie } from "#app/utils";
/**
* A wrapper for PokéRogue account API requests.
*/
export class PokerogueAccountApi extends ApiBase {
//#region Public
/**
* Request the {@linkcode AccountInfoResponse | UserInfo} of the logged in user.
* The user is identified by the {@linkcode SESSION_ID_COOKIE_NAME | session cookie}.
*/
public async getInfo(): Promise<[data: AccountInfoResponse | null, status: number]> {
try {
const response = await this.doGet("/account/info");
if (response.ok) {
const resData = (await response.json()) as AccountInfoResponse;
return [resData, response.status];
} else {
console.warn("Could not get account info!", response.status, response.statusText);
return [null, response.status];
}
} catch (err) {
console.warn("Could not get account info!", err);
return [null, 500];
}
}
/**
* Register a new account.
* @param registerData The {@linkcode AccountRegisterRequest} to send
* @returns An error message if something went wrong
*/
public async register(registerData: AccountRegisterRequest) {
try {
const response = await this.doPost("/account/register", registerData, "form-urlencoded");
if (response.ok) {
return null;
} else {
return response.text();
}
} catch (err) {
console.warn("Register failed!", err);
}
return "Unknown error!";
}
/**
* Send a login request.
* Sets the session cookie on success.
* @param loginData The {@linkcode AccountLoginRequest} to send
* @returns An error message if something went wrong
*/
public async login(loginData: AccountLoginRequest) {
try {
const response = await this.doPost("/account/login", loginData, "form-urlencoded");
if (response.ok) {
const loginResponse = (await response.json()) as AccountLoginResponse;
setCookie(SESSION_ID_COOKIE_NAME, loginResponse.token);
return null;
} else {
console.warn("Login failed!", response.status, response.statusText);
return response.text();
}
} catch (err) {
console.warn("Login failed!", err);
}
return "Unknown error!";
}
/**
* Send a logout request.
* **Always** (no matter if failed or not) removes the session cookie.
*/
public async logout() {
try {
const response = await this.doGet("/account/logout");
if (!response.ok) {
throw new Error(`${response.status}: ${response.statusText}`);
}
} catch (err) {
console.error("Log out failed!", err);
}
removeCookie(SESSION_ID_COOKIE_NAME); // we are always clearing the cookie.
}
}

View File

@ -1,17 +1,16 @@
import { Api } from "#app/plugins/api/api";
import type { LinkAccountToDiscordIdRequest } from "#app/plugins/api/models/LinkAccountToDiscordId";
import type { LinkAccountToDiscordIdRequest } from "#app/@types/PokerogueAdminApi";
import { ApiBase } from "#app/plugins/api/api-base";
export class PokerogueAdminApi extends Api {
export class PokerogueAdminApi extends ApiBase {
/**
* Links an account to a discord id.
* @param linkData The {@linkcode LinkAccountToDiscordIdRequest} to send
* @param params The {@linkcode LinkAccountToDiscordIdRequest} to send
* @returns `true` if successful, `false` if not
*/
public async linkAccountToDiscordId(linkData: LinkAccountToDiscordIdRequest) {
public async linkAccountToDiscord(params: LinkAccountToDiscordIdRequest) {
try {
const linkArr = Object.entries(linkData).map(([key, value]) => [key, String(value)]);
const params = new URLSearchParams(linkArr);
const response = await this.doPost("/admin/account/discord-link", params, "form-urlencoded");
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doPost("/admin/account/discord-link", urlSearchParams, "form-urlencoded");
if (response.ok) {
return true;

View File

@ -1,30 +1,29 @@
import type { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
import { loggedInUser } from "#app/account";
import { MAX_INT_ATTR_VALUE, SESSION_ID_COOKIE_NAME } from "#app/constants";
import { Api } from "#app/plugins/api/api";
import type { AccountInfoResponse } from "#app/plugins/api/models/AccountInfo";
import type { AccountLoginRequest, AccountLoginResponse } from "#app/plugins/api/models/AccountLogin";
import type { TitleStatsResponse } from "#app/plugins/api/models/TitleStats";
import type { UpdateAllSavedataRequest } from "#app/plugins/api/models/UpdateAllSavedata";
import type { UpdateSessionSavedataRequest } from "#app/plugins/api/models/UpdateSessionSavedata";
import type { UpdateSystemSavedataRequest } from "#app/plugins/api/models/UpdateSystemSavedata";
import type { VerifySavedataResponse } from "#app/plugins/api/models/VerifySavedata";
import type { SessionSaveData } from "#app/system/game-data";
import type { RankingEntry, ScoreboardCategory } from "#app/ui/daily-run-scoreboard";
import { removeCookie, setCookie } from "#app/utils";
import type { TitleStatsResponse } from "#app/@types/PokerogueApi";
import { ApiBase } from "#app/plugins/api/api-base";
import { PokerogueAccountApi } from "#app/plugins/api/pokerogue-account-api";
import { PokerogueAdminApi } from "#app/plugins/api/pokerogue-admin-api";
import type { AccountRegisterRequest } from "./models/AccountRegister";
import { PokerogueDailyApi } from "#app/plugins/api/pokerogue-daily-api";
import { PokerogueSavedataApi } from "#app/plugins/api/pokerogue-savedata-api";
export class PokerogueApi extends Api {
//#region Fields
/**
* A wrapper for PokéRogue API requests.
*/
export class PokerogueApi extends ApiBase {
//#region Fields∏
public readonly account: PokerogueAccountApi;
public readonly daily: PokerogueDailyApi;
public readonly admin: PokerogueAdminApi;
public readonly savedata: PokerogueSavedataApi;
//#region Public
constructor(base: string) {
super(base);
this.account = new PokerogueAccountApi(base);
this.daily = new PokerogueDailyApi(base);
this.admin = new PokerogueAdminApi(base);
this.savedata = new PokerogueSavedataApi(base);
}
/**
@ -40,358 +39,6 @@ export class PokerogueApi extends Api {
}
}
/**
* Request the {@linkcode AccountInfoResponse | UserInfo} of the logged in user.
* The user is identified by the {@linkcode SESSION_ID_COOKIE_NAME | session cookie}.
*/
public async getAccountInfo() {
try {
const response = await this.doGet("/account/info");
if (response.ok) {
return (await response.json()) as AccountInfoResponse;
} else {
console.warn("Could not get account info!", response.status, response.statusText);
return response.status;
}
} catch (err) {
console.warn("Could not get account info!", err);
return 500;
}
}
/**
* Send a login request.
* Sets the session cookie on success.
* @param loginData The {@linkcode AccountLoginRequest} to send
* @returns An error message if something went wrong
*/
public async login(loginData: AccountLoginRequest) {
try {
const response = await this.doPost("/account/login", loginData, "form-urlencoded");
if (response.ok) {
const loginResponse = (await response.json()) as AccountLoginResponse;
setCookie(SESSION_ID_COOKIE_NAME, loginResponse.token);
return null;
} else {
console.warn("Login failed!", response.status, response.statusText);
return response.text();
}
} catch (err) {
console.warn("Login failed!", err);
}
return "Unknown error!";
}
/**
* Register a new account.
* @param registerData The {@linkcode AccountRegisterRequest} to send
* @returns An error message if something went wrong
*/
public async register(registerData: AccountRegisterRequest) {
try {
const response = await this.doPost("/account/register", registerData, "form-urlencoded");
if (response.ok) {
return null;
} else {
return response.text();
}
} catch (err) {
console.warn("Register failed!", err);
}
return "Unknown error!";
}
/**
* Send a logout request.
* **Always** (no matter if failed or not) removes the session cookie.
*/
public async logout() {
try {
const response = await this.doGet("/account/logout");
if (!response.ok) {
throw new Error(`${response.status}: ${response.statusText}`);
}
} catch (err) {
console.error("Log out failed!", err);
}
removeCookie(SESSION_ID_COOKIE_NAME); // we are always clearing the cookie.
}
/**
* Request the daily-run seed.
* @returns The active daily-run seed as `string`.
*/
public async getDailySeed() {
try {
const response = await this.doGet("/daily/seed");
return response.text();
} catch (err) {
console.warn("Could not get daily-run seed!", err);
return null;
}
}
/**
* Mark a save-session as cleared.
* @param slot The save-session slot to clear.
* @param sessionId The save-session ID to clear.
* @returns The raw savedata as `string`.
*/
public async newclearSession(slot: number, sessionId: string) {
try {
const params = new URLSearchParams();
params.append("slot", String(slot));
params.append("clientSessionId", sessionId);
const response = await this.doGet(`/savedata/session/newclear?${params}`);
const json = await response.json();
return Boolean(json);
} catch (err) {
console.warn("Could not newclear session!", err);
return false;
}
}
/**
* Get a system savedata.
* @param sessionId The savedata session ID
*/
public async getSystemSavedata(sessionId: string) {
try {
const params = new URLSearchParams();
params.append("clientSessionId", sessionId);
const response = await this.doGet(`/savedata/system/get?${params}`);
const rawSavedata = await response.text();
return rawSavedata;
} catch (err) {
console.warn("Could not get system savedata!", err);
return null;
}
}
/**
* Verify if the session is valid.
* If not the {@linkcode SystemSaveData} is returned.
* @param sessionId The savedata session ID
* @returns A {@linkcode SystemSaveData} if **NOT** valid, otherwise `null`.
*/
public async verifySystemSavedata(sessionId: string) {
try {
const params = new URLSearchParams();
params.append("clientSessionId", sessionId);
const response = await this.doGet(`/savedata/system/verify?${params}`);
if (response.ok) {
const verifySavedata = (await response.json()) as VerifySavedataResponse;
if (!verifySavedata.valid) {
return verifySavedata.systemData;
}
}
} catch (err) {
console.warn("Could not verify system savedata!", err);
}
return null;
}
/**
* Update a system savedata.
* @param updateData The {@linkcode UpdateSystemSavedataRequest} to send
* @param rawSystemData The raw {@linkcode SystemSaveData}
* @returns An error message if something went wrong
*/
public async updateSystemSavedata(updateData: UpdateSystemSavedataRequest, rawSystemData: string) {
try {
const updateArr = Object.entries(updateData).map(([key, value]) => [key, String(value)]);
const params = new URLSearchParams(updateArr);
const response = await this.doPost(`/savedata/system/update?${params}`, rawSystemData);
return await response.text();
} catch (err) {
console.warn("Could not update system savedata!", err);
}
return null;
}
/**
* Get a session savedata.
* @param slotId The slot ID to load
* @param sessionId The session ID
* @returns The session as `string`
*/
public async getSessionSavedata(slotId: number, sessionId: string) {
try {
const params = new URLSearchParams();
params.append("slot", String(slotId));
params.append("clientSessionId", sessionId);
const response = await this.doGet(`/savedata/session/get?${params}`);
return await response.text();
} catch (err) {
console.warn("Could not get session savedata!", err);
return null;
}
}
/**
* Update a session savedata.
* @param updateData The {@linkcode UpdateSessionSavedataRequest} to send
* @param rawSavedata The raw savedata (as `string`)
* @returns An error message if something went wrong
*/
public async updateSessionSavedata(updateData: UpdateSessionSavedataRequest, rawSavedata: string) {
try {
const updateArr = Object.entries(updateData).map(([key, value]) => [key, String(value)]);
const params = new URLSearchParams(updateArr);
const response = await this.doPost(`/savedata/session/update?${params}`, rawSavedata);
return await response.text();
} catch (err) {
console.warn("Could not update session savedata!", err);
}
return null;
}
/**
* Delete a session savedata slot.
* @param slotId The slot ID to load
* @param sessionId The session ID
* @returns The session as `string`
*/
public async deleteSessionSavedata(slotId: number, sessionId: string) {
try {
const params = new URLSearchParams();
params.append("slot", String(slotId));
params.append("clientSessionId", sessionId);
const response = await this.doGet(`/savedata/session/delete?${params}`);
if (response.ok) {
if (loggedInUser) {
loggedInUser.lastSessionSlot = -1;
}
localStorage.removeItem(`sessionData${slotId > 0 ? slotId : ""}_${loggedInUser?.username}`);
} else {
return await response.text();
}
} catch (err) {
console.warn("Could not get session savedata!", err);
return "Unknown error";
}
}
/**
* Clears the session savedata of the given slot.
* @param slotId The slot ID
* @param trainerId The trainer ID
* @param sessionId The session ID
* @param sessionData The {@linkcode SessionSaveData} object
*/
public async clearSessionSavedata(
slotId: number,
trainerId: number,
sessionId: string,
sessionData: SessionSaveData
) {
try {
const params = new URLSearchParams();
params.append("slot", String(slotId));
params.append("trainerId", String(trainerId));
params.append("clientSessionId", sessionId);
const response = await this.doPost(`/savedata/session/clear?${params}`, sessionData);
if (response.ok) {
if (loggedInUser) {
loggedInUser!.lastSessionSlot = -1;
}
localStorage.removeItem(`sessionData${slotId > 0 ? slotId : ""}_${loggedInUser?.username}`);
}
return (await response.json()) as PokerogueApiClearSessionData;
} catch (err) {
console.warn("Could not clear session savedata!", err);
}
return null;
}
/**
* Update all savedata
* @param bodyData The {@linkcode UpdateAllSavedataRequest | request data} to send
* @returns An error message if something went wrong
*/
public async updateAllSavedata(bodyData: UpdateAllSavedataRequest) {
try {
const rawBodyData = JSON.stringify(bodyData, (_k: any, v: any) =>
typeof v === "bigint" ? (v <= MAX_INT_ATTR_VALUE ? Number(v) : v.toString()) : v
);
const response = await this.doPost("/savedata/updateall", rawBodyData);
return await response.text();
} catch (err) {
console.warn("Could not update all savedata!", err);
return null;
}
}
/**
* Get the daily rankings for a {@linkcode ScoreboardCategory}.
* @param category The {@linkcode ScoreboardCategory} to fetch.
* @param page The page number to fetch.
*/
public async getDailyRankings(category: ScoreboardCategory, page?: number) {
try {
const params = new URLSearchParams();
params.append("category", String(category));
if (page) {
params.append("page", String(page));
}
const response = await this.doGet(`/daily/rankings?${params}`);
return (await response.json()) as RankingEntry[];
} catch (err) {
console.warn("Could not get daily rankings!", err);
return null;
}
}
/**
* Get the page count of the daily rankings for a {@linkcode ScoreboardCategory}.
* @param category The {@linkcode ScoreboardCategory} to fetch.
*/
public async getDailyRankingsPageCount(category: ScoreboardCategory) {
try {
const params = new URLSearchParams();
params.append("category", String(category));
const response = await this.doGet(`/daily/rankingpagecount?${params}`);
const json = await response.json();
return Number(json);
} catch (err) {
console.warn("Could not get daily rankings page count!", err);
return 1;
}
}
/**
* Unlink the currently logged in user from Discord.
* @returns `true` if unlinking was successful, `false` if not
@ -430,17 +77,7 @@ export class PokerogueApi extends Api {
return false;
}
//#region Private
private async isLocalMode(): Promise<boolean> {
return (
((window.location.hostname === "localhost" ||
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(window.location.hostname)) &&
window.location.port !== "") ||
window.location.hostname === ""
);
}
//#endregion
}
export const pokerogueApi = new PokerogueApi(import.meta.env.VITE_SERVER_URL ?? "http://localhost:80001");
export const pokerogueApi = new PokerogueApi(import.meta.env.VITE_SERVER_URL ?? "http://localhost:8001");

View File

@ -0,0 +1,57 @@
import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#app/@types/PokerogueDailyApi";
import { ApiBase } from "#app/plugins/api/api-base";
import type { RankingEntry } from "#app/ui/daily-run-scoreboard";
/**
* A wrapper for daily-run PokéRogue API requests.
*/
export class PokerogueDailyApi extends ApiBase {
//#region Public
/**
* Request the daily-run seed.
* @returns The active daily-run seed as `string`.
*/
public async getSeed() {
try {
const response = await this.doGet("/daily/seed");
return response.text();
} catch (err) {
console.warn("Could not get daily-run seed!", err);
return null;
}
}
/**
* Get the daily rankings for a {@linkcode ScoreboardCategory}.
* @param params The {@linkcode GetDailyRankingsRequest} to send
*/
public async getRankings(params: GetDailyRankingsRequest) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doGet(`/daily/rankings?${urlSearchParams}`);
return (await response.json()) as RankingEntry[];
} catch (err) {
console.warn("Could not get daily rankings!", err);
return null;
}
}
/**
* Get the page count of the daily rankings for a {@linkcode ScoreboardCategory}.
* @param params The {@linkcode GetDailyRankingsPageCountRequest} to send.
*/
public async getRankingsPageCount(params: GetDailyRankingsPageCountRequest) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doGet(`/daily/rankingpagecount?${urlSearchParams}`);
const json = await response.json();
return Number(json);
} catch (err) {
console.warn("Could not get daily rankings page count!", err);
return 1;
}
}
}

View File

@ -0,0 +1,41 @@
import type { UpdateAllSavedataRequest } from "#app/@types/PokerogueSavedataApi";
import { MAX_INT_ATTR_VALUE } from "#app/constants";
import { ApiBase } from "#app/plugins/api/api-base";
import { PokerogueSessionSavedataApi } from "#app/plugins/api/pokerogue-session-savedata-api";
import { PokerogueSystemSavedataApi } from "#app/plugins/api/pokerogue-system-savedata-api";
/**
* A wrapper for PokéRogue savedata API requests.
*/
export class PokerogueSavedataApi extends ApiBase {
//#region Fields
public readonly system: PokerogueSystemSavedataApi;
public readonly session: PokerogueSessionSavedataApi;
//#region Public
constructor(base: string) {
super(base);
this.system = new PokerogueSystemSavedataApi(base);
this.session = new PokerogueSessionSavedataApi(base);
}
/**
* Update all savedata
* @param bodyData The {@linkcode UpdateAllSavedataRequest | request data} to send
* @returns An error message if something went wrong
*/
public async updateAll(bodyData: UpdateAllSavedataRequest) {
try {
const rawBodyData = JSON.stringify(bodyData, (_k: any, v: any) =>
typeof v === "bigint" ? (v <= MAX_INT_ATTR_VALUE ? Number(v) : v.toString()) : v
);
const response = await this.doPost("/savedata/updateall", rawBodyData);
return await response.text();
} catch (err) {
console.warn("Could not update all savedata!", err);
return null;
}
}
}

View File

@ -0,0 +1,124 @@
import type {
ClearSessionSavedataRequest,
ClearSessionSavedataResponse,
DeleteSessionSavedataRequest,
GetSessionSavedataRequest,
NewClearSessionSavedataRequest,
UpdateSessionSavedataRequest,
} from "#app/@types/PokerogueSessionSavedataApi";
import { loggedInUser } from "#app/account";
import { ApiBase } from "#app/plugins/api/api-base";
import type { SessionSaveData } from "#app/system/game-data";
/**
* A wrapper for PokéRogue session savedata API requests.
*/
export class PokerogueSessionSavedataApi extends ApiBase {
//#region Public
/**
* Mark a session as cleared aka "newclear".\
* *This is **NOT** the same as {@linkcode clear | clear()}.*
* @param params The {@linkcode NewClearSessionSavedataRequest} to send
* @returns The raw savedata as `string`.
*/
public async newclear(params: NewClearSessionSavedataRequest) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doGet(`/savedata/session/newclear?${urlSearchParams}`);
const json = await response.json();
return Boolean(json);
} catch (err) {
console.warn("Could not newclear session!", err);
return false;
}
}
/**
* Get a session savedata.
* @param params The {@linkcode GetSessionSavedataRequest} to send
* @returns The session as `string`
*/
public async get(params: GetSessionSavedataRequest) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doGet(`/savedata/session/get?${urlSearchParams}`);
return await response.text();
} catch (err) {
console.warn("Could not get session savedata!", err);
return null;
}
}
/**
* Update a session savedata.
* @param params The {@linkcode UpdateSessionSavedataRequest} to send
* @param rawSavedata The raw savedata (as `string`)
* @returns An error message if something went wrong
*/
public async update(params: UpdateSessionSavedataRequest, rawSavedata: string) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doPost(`/savedata/session/update?${urlSearchParams}`, rawSavedata);
return await response.text();
} catch (err) {
console.warn("Could not update session savedata!", err);
}
return null;
}
/**
* Delete a session savedata slot.
* @param params The {@linkcode DeleteSessionSavedataRequest} to send
* @returns The session as `string`
*/
public async delete(params: DeleteSessionSavedataRequest) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doGet(`/savedata/session/delete?${urlSearchParams}`);
if (response.ok) {
if (loggedInUser) {
loggedInUser.lastSessionSlot = -1;
}
localStorage.removeItem(`sessionData${params.slot > 0 ? params.slot : ""}_${loggedInUser?.username}`);
} else {
return await response.text();
}
} catch (err) {
console.warn("Could not get session savedata!", err);
return "Unknown error";
}
}
/**
* Clears the session savedata of the given slot.\
* *This is **NOT** the same as {@linkcode newclear | newclear()}.*
* @param params The {@linkcode ClearSessionSavedataRequest} to send
* @param sessionData The {@linkcode SessionSaveData} object
*/
public async clear(params: ClearSessionSavedataRequest, sessionData: SessionSaveData) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doPost(`/savedata/session/clear?${urlSearchParams}`, sessionData);
if (response.ok) {
if (loggedInUser) {
loggedInUser!.lastSessionSlot = -1;
}
localStorage.removeItem(`sessionData${params.slot > 0 ? params.slot : ""}_${loggedInUser?.username}`);
}
return (await response.json()) as ClearSessionSavedataResponse;
} catch (err) {
console.warn("Could not clear session savedata!", err);
}
return null;
}
}

View File

@ -0,0 +1,78 @@
import type {
GetSystemSavedataRequest,
UpdateSystemSavedataRequest,
VerifySystemSavedataRequest,
VerifySystemSavedataResponse,
} from "#app/@types/PokerogueSystemSavedataApi";
import { ApiBase } from "#app/plugins/api/api-base";
/**
* A wrapper for PokéRogue system savedata API requests.
*/
export class PokerogueSystemSavedataApi extends ApiBase {
//#region Public
/**
* Get a system savedata.
* @param params The {@linkcode GetSystemSavedataRequest} to send
* @returns The system savedata as `string` or `null` on error
*/
public async get(params: GetSystemSavedataRequest) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doGet(`/savedata/system/get?${urlSearchParams}`);
const rawSavedata = await response.text();
return rawSavedata;
} catch (err) {
console.warn("Could not get system savedata!", err);
return null;
}
}
/**
* Verify if the session is valid.
* If not the {@linkcode SystemSaveData} is returned.
* @param params The {@linkcode VerifySystemSavedataRequest} to send
* @returns A {@linkcode SystemSaveData} if **NOT** valid, otherwise `null`.
*/
public async verify(params: VerifySystemSavedataRequest) {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doGet(`/savedata/system/verify?${urlSearchParams}`);
if (response.ok) {
const verifySavedata = (await response.json()) as VerifySystemSavedataResponse;
if (!verifySavedata.valid) {
return verifySavedata.systemData;
} else {
console.warn("Invalid system savedata!");
}
}
} catch (err) {
console.warn("Could not verify system savedata!", err);
}
return null;
}
/**
* Update a system savedata.
* @param params The {@linkcode UpdateSystemSavedataRequest} to send
* @param rawSystemData The raw {@linkcode SystemSaveData}
* @returns An error message if something went wrong
*/
public async update(params: UpdateSystemSavedataRequest, rawSystemData: string) {
try {
const urSearchParams = this.toUrlSearchParams(params);
const response = await this.doPost(`/savedata/system/update?${urSearchParams}`, rawSystemData);
return await response.text();
} catch (err) {
console.warn("Could not update system savedata!", err);
}
return null;
}
}

View File

@ -397,7 +397,7 @@ export class GameData {
localStorage.setItem(`data_${loggedInUser?.username}`, encrypt(systemData, bypassLogin));
if (!bypassLogin) {
pokerogueApi.updateSystemSavedata({clientSessionId}, systemData)
pokerogueApi.savedata.system.update({clientSessionId}, systemData)
.then(error => {
this.scene.ui.savingIcon.hide();
if (error) {
@ -430,7 +430,7 @@ export class GameData {
}
if (!bypassLogin) {
pokerogueApi.getSystemSavedata(clientSessionId)
pokerogueApi.savedata.system.get({ clientSessionId })
.then(saveDataOrErr => {
if (!saveDataOrErr || saveDataOrErr.length === 0 || saveDataOrErr[0] !== "{") {
if (saveDataOrErr?.startsWith("sql: no rows in result set")) {
@ -581,6 +581,7 @@ export class GameData {
if (!Utils.isLocal) {
/**
* Networking Code DO NOT DELETE!
* Note: Might have to be migrated to `pokerogue-api.ts`
*
const response = await Utils.apiFetch("savedata/runHistory", true);
const data = await response.json();
@ -661,6 +662,7 @@ export class GameData {
return false;
}
}
NOTE: should be adopted to `pokerogue-api.ts`
*/
return true;
}
@ -705,7 +707,7 @@ export class GameData {
return true;
}
const systemData = pokerogueApi.verifySystemSavedata(clientSessionId);
const systemData = pokerogueApi.savedata.system.verify({clientSessionId});
if (systemData) {
this.scene.clearPhaseQueue();
@ -984,7 +986,7 @@ export class GameData {
};
if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`)) {
pokerogueApi.getSessionSavedata(slotId, clientSessionId)
pokerogueApi.savedata.session.get({slot: slotId, clientSessionId})
.then(async response => {
if (!response && response?.length === 0 || response?.[0] !== "{") {
console.error(response);
@ -1132,7 +1134,7 @@ export class GameData {
if (success !== null && !success) {
return resolve(false);
}
pokerogueApi.deleteSessionSavedata(slotId, clientSessionId).then(error => {
pokerogueApi.savedata.session.delete({ slot: slotId, clientSessionId }).then(error => {
if (error) {
if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
@ -1189,7 +1191,8 @@ export class GameData {
result = [true, true];
} else {
const sessionData = this.getSessionSaveData(scene);
const jsonResponse = await pokerogueApi.clearSessionSavedata(slotId, this.trainerId, clientSessionId, sessionData);
const { trainerId } = this;
const jsonResponse = await pokerogueApi.savedata.session.clear({ slot: slotId, trainerId, clientSessionId}, sessionData);
if (!jsonResponse?.error) {
result = [true, jsonResponse?.success ?? false];
@ -1309,7 +1312,7 @@ export class GameData {
console.debug("Session data saved");
if (!bypassLogin && sync) {
pokerogueApi.updateAllSavedata(request)
pokerogueApi.savedata.updateAll(request)
.then(error => {
if (sync) {
this.scene.lastSavePlayTime = 0;
@ -1359,9 +1362,9 @@ export class GameData {
let promise: Promise<any> = Promise.resolve(null);
if (dataType === GameDataType.SYSTEM) {
promise = pokerogueApi.getSystemSavedata(clientSessionId);
promise = pokerogueApi.savedata.system.get({ clientSessionId });
} else if (dataType === GameDataType.SESSION) {
promise = pokerogueApi.getSessionSavedata(slotId, clientSessionId);
promise = pokerogueApi.savedata.session.get({ slot: slotId, clientSessionId });
}
promise
@ -1457,9 +1460,9 @@ export class GameData {
const { trainerId, secretId } = this;
let updatePromise: Promise<string | null>;
if (dataType === GameDataType.SESSION) {
updatePromise = pokerogueApi.updateSessionSavedata({slot: slotId, trainerId, secretId, clientSessionId}, dataStr);
updatePromise = pokerogueApi.savedata.session.update({slot: slotId, trainerId, secretId, clientSessionId}, dataStr);
} else {
updatePromise = pokerogueApi.updateSystemSavedata({trainerId, secretId, clientSessionId}, dataStr);
updatePromise = pokerogueApi.savedata.system.update({trainerId, secretId, clientSessionId}, dataStr);
}
updatePromise
.then(error => {

View File

@ -1,7 +1,7 @@
import * as battleScene from "#app/battle-scene";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { describe, expect, it, vi } from "vitest";
import { initLoggedInUser, loggedInUser, updateUserInfo } from "../account";
import * as utils from "../utils";
describe("account", () => {
describe("initLoggedInUser", () => {
@ -27,17 +27,16 @@ describe("account", () => {
it("should fetch user info from the API if bypassLogin is false", async () => {
vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(false);
vi.spyOn(utils, "apiFetch").mockResolvedValue(
new Response(
JSON.stringify({
username: "test",
lastSessionSlot: 99,
}),
{
status: 200,
}
)
);
vi.spyOn(pokerogueApi.account, "getInfo").mockResolvedValue([
{
username: "test",
lastSessionSlot: 99,
discordId: "",
googleId: "",
hasAdminRole: false,
},
200,
]);
const [success, status] = await updateUserInfo();
@ -49,9 +48,7 @@ describe("account", () => {
it("should handle resolved API errors", async () => {
vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(false);
vi.spyOn(utils, "apiFetch").mockResolvedValue(
new Response(null, { status: 401 })
);
vi.spyOn(pokerogueApi.account, "getInfo").mockResolvedValue([null, 401]);
const [success, status] = await updateUserInfo();
@ -59,16 +56,14 @@ describe("account", () => {
expect(status).toBe(401);
});
it("should handle rejected API errors", async () => {
const consoleErrorSpy = vi.spyOn(console, "error");
it("should handle 500 API errors", async () => {
vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(false);
vi.spyOn(utils, "apiFetch").mockRejectedValue(new Error("Api failed!"));
vi.spyOn(pokerogueApi.account, "getInfo").mockResolvedValue([null, 500]);
const [success, status] = await updateUserInfo();
expect(success).toBe(false);
expect(status).toBe(500);
expect(consoleErrorSpy).toHaveBeenCalled();
});
});
});

View File

@ -1,10 +1,11 @@
import { MapModifier } from "#app/modifier/modifier";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "./utils/gameManager";
import { Moves } from "#app/enums/moves";
import { Biome } from "#app/enums/biome";
import { Mode } from "#app/ui/ui";
import { Moves } from "#app/enums/moves";
import { MapModifier } from "#app/modifier/modifier";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { Mode } from "#app/ui/ui";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import GameManager from "./utils/gameManager";
//const TIMEOUT = 20 * 1000;
@ -20,6 +21,7 @@ describe("Daily Mode", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("test-seed");
});
afterEach(() => {
@ -31,7 +33,7 @@ describe("Daily Mode", () => {
const party = game.scene.getParty();
expect(party).toHaveLength(3);
party.forEach(pkm => {
party.forEach((pkm) => {
expect(pkm.level).toBe(20);
expect(pkm.moveset.length).toBeGreaterThan(0);
});
@ -63,6 +65,7 @@ describe("Shop modifications", async () => {
game.modifiers
.addCheck("EVIOLITE")
.addCheck("MINI_BLACK_HOLE");
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("test-seed");
});
afterEach(() => {

View File

@ -1,4 +1,5 @@
import { GameModes } from "#app/game-mode";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
import { Mode } from "#app/ui/ui";
import { Biome } from "#enums/biome";
@ -7,7 +8,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import { MockClock } from "#test/utils/mocks/mockClock";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Reload", () => {
let phaserGame: Phaser.Game;
@ -25,6 +26,8 @@ describe("Reload", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(pokerogueApi, "getGameTitleStats").mockResolvedValue({ battleCount: -1, playerCount: -1 });
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("test-seed");
});
it("should not have RNG inconsistencies in a Classic run", async () => {
@ -114,8 +117,7 @@ describe("Reload", () => {
}, 20000);
it("should not have RNG inconsistencies at a Daily run double battle", async () => {
game.override
.battleType("double");
game.override.battleType("double");
await game.dailyMode.startBattle();
const preReloadRngState = Phaser.Math.RND.state();
@ -128,9 +130,7 @@ describe("Reload", () => {
}, 20000);
it("should not have RNG inconsistencies at a Daily run Gym Leader fight", async () => {
game.override
.battleType("single")
.startingWave(40);
game.override.battleType("single").startingWave(40);
await game.dailyMode.startBattle();
const preReloadRngState = Phaser.Math.RND.state();
@ -143,9 +143,7 @@ describe("Reload", () => {
}, 20000);
it("should not have RNG inconsistencies at a Daily run regular trainer fight", async () => {
game.override
.battleType("single")
.startingWave(45);
game.override.battleType("single").startingWave(45);
await game.dailyMode.startBattle();
const preReloadRngState = Phaser.Math.RND.state();
@ -158,9 +156,7 @@ describe("Reload", () => {
}, 20000);
it("should not have RNG inconsistencies at a Daily run wave 50 Boss fight", async () => {
game.override
.battleType("single")
.startingWave(50);
game.override.battleType("single").startingWave(50);
await game.runToFinalBossEncounter([Species.BULBASAUR], GameModes.DAILY);
const preReloadRngState = Phaser.Math.RND.state();

View File

@ -77,7 +77,7 @@ export default class GameWrapper {
constructor(phaserGame: Phaser.Game, bypassLogin: boolean) {
Phaser.Math.RND.sow([ 'test' ]);
vi.spyOn(Utils, "apiFetch", "get").mockReturnValue(fetch);
// vi.spyOn(Utils, "apiFetch", "get").mockReturnValue(fetch);
if (bypassLogin) {
vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(true);
}

View File

@ -62,7 +62,7 @@ export default class AdminUiHandler extends FormModalUiHandler {
return onFail("Discord Id is required");
}
const [ usernameInput, discordIdInput ] = this.inputs;
pokerogueApi.admin.linkAccountToDiscordId({ username: usernameInput.text, discordId: discordIdInput.text })
pokerogueApi.admin.linkAccountToDiscord({ username: usernameInput.text, discordId: discordIdInput.text })
.then(isSuccess => {
if (isSuccess) {
usernameInput.setText("");

View File

@ -192,9 +192,9 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container {
}
Utils.executeIf(category !== this.category || this.pageCount === undefined,
() => pokerogueApi.getDailyRankingsPageCount(category).then(count => this.pageCount = count)
() => pokerogueApi.daily.getRankingsPageCount(category).then(count => this.pageCount = count)
).then(() => {
pokerogueApi.getDailyRankings(category, page)
pokerogueApi.daily.getRankings(category, page)
.then(rankings => {
this.page = page;
this.category = category;

View File

@ -136,7 +136,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler {
const [usernameInput, passwordInput] = this.inputs;
pokerogueApi.login({ username: usernameInput.text, password: passwordInput.text }).then(error => {
pokerogueApi.account.login({ username: usernameInput.text, password: passwordInput.text }).then(error => {
if (!error) {
originalLoginAction && originalLoginAction();
} else {

View File

@ -581,7 +581,7 @@ export default class MenuUiHandler extends MessageUiHandler {
success = true;
const doLogout = () => {
ui.setMode(Mode.LOADING, {
buttonActions: [], fadeOut: () => pokerogueApi.logout().then(() => {
buttonActions: [], fadeOut: () => pokerogueApi.account.logout().then(() => {
updateUserInfo().then(() => this.scene.reset(true, true));
})
});

View File

@ -107,10 +107,10 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler {
return onFail(i18next.t("menu:passwordNotMatchingConfirmPassword"));
}
const [usernameInput, passwordInput] = this.inputs;
pokerogueApi.register({ username: usernameInput.text, password: passwordInput.text })
pokerogueApi.account.register({ username: usernameInput.text, password: passwordInput.text })
.then(registerError => {
if (!registerError) {
pokerogueApi.login({ username: usernameInput.text, password: passwordInput.text })
pokerogueApi.account.login({ username: usernameInput.text, password: passwordInput.text })
.then(loginError => {
if (!loginError) {
originalRegistrationAction && originalRegistrationAction();