Skip to content

Commit

Permalink
[Refactor] Pokerogue API client (#4583)
Browse files Browse the repository at this point in the history
* start migrating Utils.apiFetch to api class

* move dailyranking to api

* use api in title-ui-handler

* remove: Utils.apiFetch

* migrate `updateSystemSavedata` to api

* migrate clear session savedata to api

* migrate updateAllSavedata to api

* migrate `updateSessionSavedata` to api

* rename `api` to `pokerogue-api`

* migrate unlink discord to pokerogue-api

* migrate unlink google to pokerogue-api

* update pokerogue-api login

* migrate register account to pokerogue-api

* remove Utils.apiPost

* reset overrides.ts

* chore: cleanup

* fix env.development

* fix circular dependencies with api

* fix gamedata verify missing await

* fix daily api calls in daily-run-scorebard

* fix discord-link request body being empty

there was a double `toUrlSearchParams()` call involved

* add pokerogue-api test coverge

* add test-utils `getApiBaseUrl()` method

* add pokerogue-admin-api test coverage

* add pokerogue-account-api test coverage

* add pokerogue-daily-api test coverage

* add pokerogue-savedata-api test coverage

* fix some test describes

* add pokerogue-session-savedata-api test coverage

* add pokerogue-system-savedata-api test coverage

* fix tests

* fix tryExportData

thanks @MokaStitcher

* chore: fix menu-ui-handlers.ts

* fix admin-ui-handler (types)

* extend test-coverage for admin-api

* remove outdated code

* skip some clowning-around-encounter tests if events are active

this is not a permanent solution

* Update src/system/game-data.ts

Co-authored-by: PigeonBar <[email protected]>

* Revert "skip some clowning-around-encounter tests if events are active"

This reverts commit a97dafe.

* mark `localServerUrl` and `apiUrl` as deprecated

in `utils.ts`

---------

Co-authored-by: NightKev <[email protected]>
Co-authored-by: PigeonBar <[email protected]>
  • Loading branch information
3 people authored Nov 4, 2024
1 parent a70f086 commit 7a0c88e
Show file tree
Hide file tree
Showing 46 changed files with 2,036 additions and 289 deletions.
2 changes: 1 addition & 1 deletion global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ declare global {
*
* To set up your own server in a test see `game_data.test.ts`
*/
var i18nServer: SetupServerApi;
var server: SetupServerApi;
}
17 changes: 17 additions & 0 deletions src/@types/PokerogueAccountApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { UserInfo } from "#app/@types/UserInfo";

export interface AccountInfoResponse extends UserInfo {}

export interface AccountLoginRequest {
username: string;
password: string;
}

export interface AccountLoginResponse {
token: string;
}

export interface AccountRegisterRequest {
username: string;
password: string;
}
31 changes: 31 additions & 0 deletions src/@types/PokerogueAdminApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export interface LinkAccountToDiscordIdRequest {
username: string;
discordId: string;
}

export interface UnlinkAccountFromDiscordIdRequest {
username: string;
discordId: string;
}

export interface LinkAccountToGoogledIdRequest {
username: string;
googleId: string;
}

export interface UnlinkAccountFromGoogledIdRequest {
username: string;
googleId: string;
}

export interface SearchAccountRequest {
username: string;
}

export interface SearchAccountResponse {
username: string;
discordId: string;
googleId: string;
lastLoggedIn: string;
registered: string;
}
4 changes: 4 additions & 0 deletions src/@types/PokerogueApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface TitleStatsResponse {
playerCount: number;
battleCount: number;
}
10 changes: 10 additions & 0 deletions src/@types/PokerogueDailyApi.ts
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 8 additions & 0 deletions src/@types/PokerogueSavedataApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { SessionSaveData, SystemSaveData } from "#app/system/game-data";

export interface UpdateAllSavedataRequest {
system: SystemSaveData;
session: SessionSaveData;
sessionSlotId: number;
clientSessionId: string;
}
39 changes: 39 additions & 0 deletions src/@types/PokerogueSessionSavedataApi.ts
Original file line number Diff line number Diff line change
@@ -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;
}
20 changes: 20 additions & 0 deletions src/@types/PokerogueSystemSavedataApi.ts
Original file line number Diff line number Diff line change
@@ -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;
}
7 changes: 7 additions & 0 deletions src/@types/UserInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface UserInfo {
username: string;
lastSessionSlot: number;
discordId: string;
googleId: string;
hasAdminRole: boolean;
}
9 changes: 0 additions & 9 deletions src/@types/pokerogue-api.ts

This file was deleted.

26 changes: 8 additions & 18 deletions src/account.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import type { UserInfo } from "#app/@types/UserInfo";
import { bypassLogin } from "./battle-scene";
import * as Utils from "./utils";

export interface UserInfo {
username: string;
lastSessionSlot: integer;
discordId: string;
googleId: string;
hasAdminRole: boolean;
}

export let loggedInUser: UserInfo | null = null;
// This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting
export const clientSessionId = Utils.randomString(32);
Expand Down Expand Up @@ -43,18 +37,14 @@ export function updateUserInfo(): Promise<[boolean, integer]> {
});
return resolve([ true, 200 ]);
}
Utils.apiFetch("account/info", true).then(response => {
if (!response.ok) {
resolve([ false, response.status ]);
pokerogueApi.account.getInfo().then(([ accountInfo, status ]) => {
if (!accountInfo) {
resolve([ false, status ]);
return;
} else {
loggedInUser = accountInfo;
resolve([ true, 200 ]);
}
return response.json();
}).then(jsonResponse => {
loggedInUser = jsonResponse;
resolve([ true, 200 ]);
}).catch(err => {
console.error(err);
resolve([ false, 500 ]);
});
});
}
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ export const PLAYER_PARTY_MAX_SIZE: number = 6;

/** Whether to use seasonal splash messages in general */
export const USE_SEASONAL_SPLASH_MESSAGES: boolean = false;

/** Name of the session ID cookie */
export const SESSION_ID_COOKIE_NAME: string = "pokerogue_sessionId";

/** Max value for an integer attribute in {@linkcode SystemSaveData} */
export const MAX_INT_ATTR_VALUE = 0x80000000;
12 changes: 4 additions & 8 deletions src/data/daily-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Starter } from "#app/ui/starter-select-ui-handler";
import * as Utils from "#app/utils";
import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";

export interface DailyRunConfig {
seed: integer;
Expand All @@ -14,14 +15,9 @@ export interface DailyRunConfig {

export function fetchDailyRunSeed(): Promise<string | null> {
return new Promise<string | null>((resolve, reject) => {
Utils.apiFetch("daily/seed").then(response => {
if (!response.ok) {
resolve(null);
return;
}
return response.text();
}).then(seed => resolve(seed ?? null))
.catch(err => reject(err));
pokerogueApi.daily.getSeed().then(dailySeed => {
resolve(dailySeed);
});
});
}

Expand Down
8 changes: 4 additions & 4 deletions src/phases/game-over-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as Utils from "#app/utils";
import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";

export class GameOverPhase extends BattlePhase {
private victory: boolean;
Expand Down Expand Up @@ -176,10 +177,9 @@ export class GameOverPhase extends BattlePhase {
If Online, execute apiFetch as intended
If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */
if (this.victory) {
if (!Utils.isLocal) {
Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true)
.then(response => response.json())
.then(newClear => doGameOver(newClear));
if (!Utils.isLocal || Utils.isLocalServerConnected) {
pokerogueApi.savedata.session.newclear({ slot: this.scene.sessionSlotId, clientSessionId })
.then((success) => doGameOver(!!success));
} else {
this.scene.gameData.offlineNewClear(this.scene).then(result => {
doGameOver(result);
Expand Down
2 changes: 1 addition & 1 deletion src/phases/title-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export class TitlePhase extends Phase {
};

// If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date.
if (!Utils.isLocal) {
if (!Utils.isLocal || Utils.isLocalServerConnected) {
fetchDailyRunSeed().then(seed => {
if (seed) {
generateDaily(seed);
Expand Down
91 changes: 91 additions & 0 deletions src/plugins/api/api-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { SESSION_ID_COOKIE_NAME } from "#app/constants";
import { getCookie } from "#app/utils";

type DataType = "json" | "form-urlencoded";

export abstract class ApiBase {
//#region Fields

public readonly ERR_GENERIC: string = "There was an error";

protected readonly base: string;

//#region Public

constructor(base: string) {
this.base = base;
}

//#region Protected

/**
* Send a GET request.
* @param path The path to send the request to.
*/
protected async doGet(path: string) {
return this.doFetch(path, { method: "GET" });
}

/**
* Send a POST request.
* @param path THe path to send the request to.
* @param bodyData The body-data to send.
* @param dataType The data-type of the {@linkcode bodyData}.
*/
protected async doPost<D = undefined>(path: string, bodyData?: D, dataType: DataType = "json") {
let body: string | undefined = undefined;
const headers: HeadersInit = {};

if (bodyData) {
if (dataType === "json") {
body = typeof bodyData === "string" ? bodyData : JSON.stringify(bodyData);
headers["Content-Type"] = "application/json";
} else if (dataType === "form-urlencoded") {
if (bodyData instanceof Object) {
body = this.toUrlSearchParams(bodyData).toString();
} else {
console.warn("Could not add body data to form-urlencoded!", bodyData);
}
headers["Content-Type"] = "application/x-www-form-urlencoded";
} else {
console.warn(`Unsupported data type: ${dataType}`);
body = String(bodyData);
headers["Content-Type"] = "text/plain";
}
}

return await this.doFetch(path, { method: "POST", body, headers });
}

/**
* A generic request helper.
* @param path The path to send the request to.
* @param config The request {@linkcode RequestInit | Configuration}.
*/
protected async doFetch(path: string, config: RequestInit): Promise<Response> {
config.headers = {
...config.headers,
Authorization: getCookie(SESSION_ID_COOKIE_NAME),
"Content-Type": config.headers?.["Content-Type"] ?? "application/json",
};

console.log(`Sending ${config.method ?? "GET"} request to: `, this.base + path, config);

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);
}
}
Loading

0 comments on commit 7a0c88e

Please sign in to comment.