강력한 API 타입 체크
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
import { POST } from "../base";
|
||||||
|
import type { structure } from "../../apiStructure/sammoRootAPI";
|
||||||
|
import type { ExtractError, ExtractQuery, ExtractResponse } from "../../apiStructure/defs";
|
||||||
|
|
||||||
|
type BaseAPI = typeof structure.Login.LoginByID;
|
||||||
|
type RType = ExtractResponse<BaseAPI>;
|
||||||
|
type EType = ExtractError<BaseAPI>;
|
||||||
|
type QType = ExtractQuery<BaseAPI>;
|
||||||
|
|
||||||
|
export class LoginByID extends POST<RType, EType, QType>{
|
||||||
|
protected LoginByID = Symbol("LoginByID");//TODO: remove this
|
||||||
|
protected override async api(query: QType): Promise<RType | EType | true> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { POST } from "../base";
|
||||||
|
import type { structure } from "../../apiStructure/sammoRootAPI";
|
||||||
|
import type { ExtractError, ExtractQuery, ExtractResponse } from "../../apiStructure/defs";
|
||||||
|
|
||||||
|
type BaseAPI = typeof structure.Login.LoginByToken;
|
||||||
|
type RType = ExtractResponse<BaseAPI>;
|
||||||
|
type EType = ExtractError<BaseAPI>;
|
||||||
|
type QType = ExtractQuery<BaseAPI>;
|
||||||
|
|
||||||
|
export class LoginByToken extends POST<RType, EType, QType>{
|
||||||
|
protected LoginByToken = Symbol("LoginByToken");//TODO: remove this
|
||||||
|
protected override async api(query: QType): Promise<RType | EType | true> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { POST } from "../base";
|
||||||
|
import type { structure } from "../../apiStructure/sammoRootAPI";
|
||||||
|
import type { ExtractError, ExtractQuery, ExtractResponse } from "../../apiStructure/defs";
|
||||||
|
|
||||||
|
type BaseAPI = typeof structure.Login.ReqNonce;
|
||||||
|
type RType = ExtractResponse<BaseAPI>;
|
||||||
|
type EType = ExtractError<BaseAPI>;
|
||||||
|
type QType = ExtractQuery<BaseAPI>;
|
||||||
|
|
||||||
|
export class ReqNonce extends POST<RType, EType, QType>{
|
||||||
|
protected ReqNonce = Symbol("ReqNonce");//TODO: remove this
|
||||||
|
protected override async api(query: QType): Promise<RType | EType | true> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { structure } from "../../apiStructure/sammoRootAPI";
|
||||||
|
import { APINamespaceType } from "../base";
|
||||||
|
import { LoginByID } from "./LoginByID";
|
||||||
|
import { LoginByToken } from "./LoginByToken";
|
||||||
|
import { ReqNonce } from "./ReqNonce";
|
||||||
|
|
||||||
|
export const Login = {
|
||||||
|
LoginByID,
|
||||||
|
LoginByToken,
|
||||||
|
ReqNonce,
|
||||||
|
} satisfies APINamespaceType<typeof structure.Login>;
|
||||||
+32
-19
@@ -1,22 +1,8 @@
|
|||||||
|
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
|
import type { APICallT, Callable, DefAPINamespace, EmptyAPICallT, HttpMethod, InvalidResponse, RawArgType, ValidResponse, recoveryMethod } from '../apiStructure/defs';
|
||||||
export interface ValidResponse {
|
|
||||||
result: true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type recoveryMethod = 'refreshEntirePage' | 'retryAPI' | 'gameLogin' | 'gatewayLogin' | 'gatewayPIN';
|
|
||||||
|
|
||||||
export interface InvalidResponse {
|
|
||||||
result: false;
|
|
||||||
reason: string;
|
|
||||||
recovery?: recoveryMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const treatedSpecial = Symbol('treatedSpecial');
|
export const treatedSpecial = Symbol('treatedSpecial');
|
||||||
export type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head';
|
|
||||||
export type RawArgType = Record<string, unknown> | Record<string, unknown>[] | undefined;
|
|
||||||
|
|
||||||
export type APINamespace = {
|
export type APINamespace = {
|
||||||
[key: string]: APINamespace
|
[key: string]: APINamespace
|
||||||
| GET<any, any, any>
|
| GET<any, any, any>
|
||||||
@@ -29,14 +15,14 @@ export type APINamespace = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class APIExecuter<R extends ValidResponse, E extends InvalidResponse, Q extends RawArgType>{
|
export abstract class APIExecuter<R extends ValidResponse, E extends InvalidResponse, Q extends RawArgType>{
|
||||||
readonly abstract reqType: HttpMethod | HttpMethod[];
|
readonly abstract reqType: HttpMethod;
|
||||||
protected abstract parseQuery(expressReq: Request): Promise<Q>;
|
protected abstract parseQuery(expressReq: Request): Promise<Q>;
|
||||||
protected abstract api(query: Q, expressReq: Request, expressRes: Response): Promise<R | E | typeof treatedSpecial>;
|
protected abstract api(query: Q, expressReq: Request, expressRes: Response): Promise<R | E | true>;
|
||||||
public async run(expressReq: Request, expressRes: Response): Promise<void> {
|
public async run(expressReq: Request, expressRes: Response): Promise<void> {
|
||||||
const query = await this.parseQuery(expressReq);
|
const query = await this.parseQuery(expressReq);
|
||||||
//TODO: middleware (인증, 요구사항, 아마도 decorator)
|
//TODO: middleware (인증, 요구사항, 아마도 decorator)
|
||||||
const result = await this.api(query, expressReq, expressRes);
|
const result = await this.api(query, expressReq, expressRes);
|
||||||
if (result === treatedSpecial) {
|
if (result === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
expressRes.json(result);
|
expressRes.json(result);
|
||||||
@@ -77,4 +63,31 @@ export abstract class PATCH<R extends ValidResponse, E extends InvalidResponse,
|
|||||||
|
|
||||||
export abstract class HEAD<R extends ValidResponse, E extends InvalidResponse, Q extends RawArgType> extends APIBodyParseExecuter<R, E, Q>{
|
export abstract class HEAD<R extends ValidResponse, E extends InvalidResponse, Q extends RawArgType> extends APIBodyParseExecuter<R, E, Q>{
|
||||||
readonly reqType = 'head';
|
readonly reqType = 'head';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function raiseError(reason: string, recovery?: recoveryMethod): InvalidResponse {
|
||||||
|
if (recovery) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason,
|
||||||
|
recovery,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
reason,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetClassGenerator<T> = {
|
||||||
|
prototype: T
|
||||||
|
};
|
||||||
|
export type APIServerType<T extends Callable> =
|
||||||
|
T extends APICallT<infer Q, infer R, infer E> ? GetClassGenerator<APIExecuter<R, E, Q>> :
|
||||||
|
never;
|
||||||
|
export type APINamespaceType<T extends DefAPINamespace> = {
|
||||||
|
[K in keyof T]:
|
||||||
|
T[K] extends Callable ? APIServerType<T[K]> :
|
||||||
|
T[K] extends DefAPINamespace ? APINamespaceType<T[K]> :
|
||||||
|
never;
|
||||||
|
};
|
||||||
+5
-12
@@ -6,20 +6,13 @@ export function buildAPISystem<N extends APINamespace, Q extends APIExecuter<any
|
|||||||
for (const [key, value] of Object.entries(api)) {
|
for (const [key, value] of Object.entries(api)) {
|
||||||
if (typeof value === 'function') {
|
if (typeof value === 'function') {
|
||||||
const executer = new value() as Q;
|
const executer = new value() as Q;
|
||||||
if(!executer.reqType){
|
if (!executer.reqType) {
|
||||||
throw new Error('APIExecuter.reqType is not defined');
|
throw new Error('APIExecuter.reqType is not defined');
|
||||||
}
|
}
|
||||||
if(!Array.isArray(executer.reqType)){
|
router[executer.reqType](key, async (req, res) => {
|
||||||
router[executer.reqType](key, async (req, res) => {
|
await executer.run(req, res);
|
||||||
await executer.run(req, res);
|
});
|
||||||
});
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for(const type of new Set(executer.reqType)){
|
|
||||||
router[type](key, async (req, res) => {
|
|
||||||
await executer.run(req, res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
router.use(key, buildAPISystem(value));
|
router.use(key, buildAPISystem(value));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import ky from 'ky';
|
import { APIExecuter } from "../api/base";
|
||||||
import type { APIExecuter, APINamespace, HttpMethod, InvalidResponse, RawArgType, ValidResponse } from '../api/base.js';
|
|
||||||
import isArray from 'lodash-es/isArray.js';
|
|
||||||
import isEmpty from 'lodash-es/isEmpty.js';
|
|
||||||
|
|
||||||
const apiPath = process.env.API_ROOT_PATH ?? process.env.VITE_API_ROOT_PATH ?? '/api';
|
export type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head';
|
||||||
|
export type RawArgType = Record<string, unknown> | Record<string, unknown>[] | undefined;
|
||||||
|
|
||||||
interface BasicAPICallT<
|
|
||||||
|
export interface BasicAPICallT<
|
||||||
ArgType extends RawArgType,
|
ArgType extends RawArgType,
|
||||||
ResultType extends ValidResponse,
|
ResultType extends ValidResponse,
|
||||||
ErrorType extends InvalidResponse
|
ErrorType extends InvalidResponse
|
||||||
@@ -15,7 +14,7 @@ interface BasicAPICallT<
|
|||||||
(args: ArgType, returnError: true): Promise<ResultType | ErrorType>;
|
(args: ArgType, returnError: true): Promise<ResultType | ErrorType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EmptyAPICallT<ResultType extends ValidResponse, ErrorType extends InvalidResponse> {
|
export interface EmptyAPICallT<ResultType extends ValidResponse, ErrorType extends InvalidResponse> {
|
||||||
(): Promise<ResultType>;
|
(): Promise<ResultType>;
|
||||||
(args: undefined): Promise<ResultType>;
|
(args: undefined): Promise<ResultType>;
|
||||||
(args: undefined, returnError: false): Promise<ResultType>;
|
(args: undefined, returnError: false): Promise<ResultType>;
|
||||||
@@ -65,8 +64,7 @@ export async function GET<
|
|||||||
ErrorType extends InvalidResponse,
|
ErrorType extends InvalidResponse,
|
||||||
ArgType extends undefined = undefined
|
ArgType extends undefined = undefined
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
||||||
console.error(`Can't directly call GET. ${args}, ${returnError}. Use auto-generated path API.`);
|
throw `Can't directly call GET. ${args}, ${returnError}. Use auto-generated path API.`
|
||||||
return callClientAPI<ResultType, ErrorType>("get", [], args, undefined, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
export async function POST<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
||||||
@@ -86,8 +84,7 @@ export async function POST<
|
|||||||
ErrorType extends InvalidResponse,
|
ErrorType extends InvalidResponse,
|
||||||
ArgType extends RawArgType = RawArgType
|
ArgType extends RawArgType = RawArgType
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
||||||
console.error(`Can't directly call POST. ${args}, ${returnError}. Use auto-generated path API.`);
|
throw `Can't directly call POST. ${args}, ${returnError}. Use auto-generated path API.`
|
||||||
return callClientAPI<ResultType, ErrorType>("post", [], args, undefined, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function PUT<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
export async function PUT<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
||||||
@@ -107,8 +104,7 @@ export async function PUT<
|
|||||||
ErrorType extends InvalidResponse,
|
ErrorType extends InvalidResponse,
|
||||||
ArgType extends RawArgType = RawArgType
|
ArgType extends RawArgType = RawArgType
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
||||||
console.error(`Can't directly call PUT. ${args}, ${returnError}. Use auto-generated path API.`);
|
throw `Can't directly call PUT. ${args}, ${returnError}. Use auto-generated path API.`;
|
||||||
return callClientAPI<ResultType, ErrorType>("put", [], args, undefined, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function PATCH<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
export async function PATCH<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
||||||
@@ -128,8 +124,7 @@ export async function PATCH<
|
|||||||
ErrorType extends InvalidResponse,
|
ErrorType extends InvalidResponse,
|
||||||
ArgType extends RawArgType = RawArgType
|
ArgType extends RawArgType = RawArgType
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
||||||
console.error(`Can't directly call PATCH. ${args}, ${returnError}. Use auto-generated path API.`);
|
throw `Can't directly call PATCH. ${args}, ${returnError}. Use auto-generated path API.`
|
||||||
return callClientAPI<ResultType, ErrorType>("patch", [], args, undefined, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function HEAD<ResultType extends ValidResponse, ArgType extends undefined = undefined>(
|
export async function HEAD<ResultType extends ValidResponse, ArgType extends undefined = undefined>(
|
||||||
@@ -149,8 +144,7 @@ export async function HEAD<
|
|||||||
ErrorType extends InvalidResponse,
|
ErrorType extends InvalidResponse,
|
||||||
ArgType extends undefined = undefined
|
ArgType extends undefined = undefined
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
||||||
console.error(`Can't directly call HEAD. ${args}, ${returnError}. Use auto-generated path API.`);
|
throw `Can't directly call HEAD. ${args}, ${returnError}. Use auto-generated path API.`;
|
||||||
return callClientAPI<ResultType, ErrorType>("head", [], args, undefined, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function DELETE<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
export async function DELETE<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
||||||
@@ -170,85 +164,39 @@ export async function DELETE<
|
|||||||
ErrorType extends InvalidResponse,
|
ErrorType extends InvalidResponse,
|
||||||
ArgType extends RawArgType = RawArgType
|
ArgType extends RawArgType = RawArgType
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
||||||
console.error(`Can't directly call DELETE. ${args}, ${returnError}. Use auto-generated path API.`);
|
throw `Can't directly call DELETE. ${args}, ${returnError}. Use auto-generated path API.`;
|
||||||
return callClientAPI<ResultType, ErrorType>("patch", [], args, undefined, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function callClientAPI<ResultType extends ValidResponse>(
|
export interface ValidResponse {
|
||||||
method: HttpMethod,
|
result: true;
|
||||||
path: string | string[],
|
|
||||||
args: RawArgType,
|
|
||||||
paramArgs: Record<string, string | number> | undefined
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function callClientAPI<ResultType extends ValidResponse>(
|
|
||||||
method: HttpMethod,
|
|
||||||
path: string | string[],
|
|
||||||
args: RawArgType,
|
|
||||||
paramArgs: Record<string, string | number> | undefined,
|
|
||||||
returnError: false
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function callClientAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(
|
|
||||||
method: HttpMethod,
|
|
||||||
path: string | string[],
|
|
||||||
args: RawArgType,
|
|
||||||
paramArgs: Record<string, string | number> | undefined,
|
|
||||||
returnError: true
|
|
||||||
): Promise<ResultType | ErrorType>;
|
|
||||||
export async function callClientAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(
|
|
||||||
method: HttpMethod,
|
|
||||||
path: string | string[],
|
|
||||||
args: RawArgType,
|
|
||||||
paramArgs: Record<string, string | number> | undefined,
|
|
||||||
returnError = false
|
|
||||||
): Promise<ResultType | ErrorType> {
|
|
||||||
if (isArray(path)) {
|
|
||||||
path = path.join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args && isEmpty(args)) {
|
|
||||||
args = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = (await (() => {
|
|
||||||
if (method == "get") {
|
|
||||||
return ky(apiPath, {
|
|
||||||
searchParams: {
|
|
||||||
...paramArgs,
|
|
||||||
...(args as typeof paramArgs),
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
method,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
timeout: 30000,
|
|
||||||
retry: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return ky(apiPath, {
|
|
||||||
searchParams: {
|
|
||||||
...paramArgs,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
method,
|
|
||||||
json: args,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
timeout: 30000,
|
|
||||||
retry: 0,
|
|
||||||
});
|
|
||||||
})().json()) as ErrorType | ResultType;
|
|
||||||
|
|
||||||
if (!result.result) {
|
|
||||||
if (returnError) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
throw result.reason;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: type이 하나 만들어져야 함. 이것은 typescript의 type system으로.
|
export type recoveryMethod = 'refreshEntirePage' | 'retryAPI' | 'gameLogin' | 'gatewayLogin' | 'gateway2FA' | 'gameQuota';
|
||||||
//TODO: const로 실제 구조가 하나 더 만들어 져야 함. 이것은 위의 type을 satisfy하여야 함.
|
export interface InvalidResponse {
|
||||||
|
result: false;
|
||||||
|
reason: string;
|
||||||
|
recovery?: recoveryMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtractValid<T> = T extends ValidResponse ? T : never;
|
||||||
|
type ExtractInvalid<T> = T extends InvalidResponse ? T : never;
|
||||||
|
|
||||||
|
export type Callable = (...args: any)=>any;
|
||||||
|
|
||||||
|
export type ExtractResponse<T extends Callable> = ExtractValid<Awaited<ReturnType<T>>>;
|
||||||
|
export type ExtractError<T extends Callable> = ExtractInvalid<Awaited<ReturnType<T>>>;
|
||||||
|
export type ExtractQuery<T extends Callable> = Parameters<T>[0];
|
||||||
|
|
||||||
|
export type DefAPINamespace = {
|
||||||
|
[key: string]: DefAPINamespace
|
||||||
|
| typeof GET<any, any, any>
|
||||||
|
| typeof POST<any, any, any>
|
||||||
|
| typeof PUT<any, any, any>
|
||||||
|
| typeof DELETE<any, any, any>
|
||||||
|
| typeof PATCH<any, any, any>
|
||||||
|
| typeof HEAD<any, any, any>
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type APICompatType<T extends Callable> = T extends APICallT<infer A, infer R, infer E> ? APICallT<A, R, E> : never;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { type APICallT, GET, POST } from "./defs";
|
||||||
|
|
||||||
|
export type LoginResponse = {
|
||||||
|
result: true,
|
||||||
|
nextToken: [number, string] | undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoginFailed = {
|
||||||
|
result: false,
|
||||||
|
reqOTP: boolean,
|
||||||
|
reason: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type AutoLoginNonceResponse = {
|
||||||
|
result: true,
|
||||||
|
loginNonce: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AutoLoginResponse = {
|
||||||
|
result: true,
|
||||||
|
nextToken: [number, string] | undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type AutoLoginFailed = {
|
||||||
|
result: false,
|
||||||
|
silent: boolean,
|
||||||
|
reason: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const structure = {
|
||||||
|
Login: {
|
||||||
|
LoginByID: POST as APICallT<{
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
}, LoginResponse, LoginFailed>,
|
||||||
|
LoginByToken: POST as APICallT<{
|
||||||
|
hashedToken: string,
|
||||||
|
token_id: number,
|
||||||
|
}, AutoLoginResponse, AutoLoginFailed>,
|
||||||
|
ReqNonce: GET as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import isArray from "lodash-es/isArray";
|
||||||
|
import isEmpty from "lodash-es/isEmpty";
|
||||||
|
import ky from "ky";
|
||||||
|
import type { HttpMethod, InvalidResponse, RawArgType, ValidResponse } from "../apiStructure/defs";
|
||||||
|
|
||||||
|
const apiPath = process.env.API_ROOT_PATH ?? process.env.VITE_API_ROOT_PATH ?? '/api';
|
||||||
|
|
||||||
|
export async function callClientAPI<ResultType extends ValidResponse>(
|
||||||
|
method: HttpMethod,
|
||||||
|
path: string | string[],
|
||||||
|
args: RawArgType,
|
||||||
|
paramArgs: Record<string, string | number> | undefined
|
||||||
|
): Promise<ResultType>;
|
||||||
|
export async function callClientAPI<ResultType extends ValidResponse>(
|
||||||
|
method: HttpMethod,
|
||||||
|
path: string | string[],
|
||||||
|
args: RawArgType,
|
||||||
|
paramArgs: Record<string, string | number> | undefined,
|
||||||
|
returnError: false
|
||||||
|
): Promise<ResultType>;
|
||||||
|
export async function callClientAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(
|
||||||
|
method: HttpMethod,
|
||||||
|
path: string | string[],
|
||||||
|
args: RawArgType,
|
||||||
|
paramArgs: Record<string, string | number> | undefined,
|
||||||
|
returnError: true
|
||||||
|
): Promise<ResultType | ErrorType>;
|
||||||
|
export async function callClientAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(
|
||||||
|
method: HttpMethod,
|
||||||
|
path: string | string[],
|
||||||
|
args: RawArgType,
|
||||||
|
paramArgs: Record<string, string | number> | undefined,
|
||||||
|
returnError = false
|
||||||
|
): Promise<ResultType | ErrorType> {
|
||||||
|
if (isArray(path)) {
|
||||||
|
path = path.join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args && isEmpty(args)) {
|
||||||
|
args = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = (await (() => {
|
||||||
|
if (method == "get") {
|
||||||
|
return ky(apiPath, {
|
||||||
|
searchParams: {
|
||||||
|
...paramArgs,
|
||||||
|
...(args as typeof paramArgs),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
timeout: 30000,
|
||||||
|
retry: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ky(apiPath, {
|
||||||
|
searchParams: {
|
||||||
|
...paramArgs,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
json: args,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
timeout: 30000,
|
||||||
|
retry: 0,
|
||||||
|
});
|
||||||
|
})().json()) as ErrorType | ResultType;
|
||||||
|
|
||||||
|
if (!result.result) {
|
||||||
|
if (returnError) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw result.reason;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -1,51 +1,10 @@
|
|||||||
import { APIPathGen } from "../api/APIPathGen";
|
import { APIPathGen } from "../api/APIPathGen";
|
||||||
import { RawArgType } from "../api/base";
|
import { extractHttpMethod } from "../apiStructure/defs";
|
||||||
import { APICallT, APITail, GET, POST, callClientAPI, extractHttpMethod } from "./clientAPI";
|
import type { APITail, RawArgType } from "../apiStructure/defs";
|
||||||
|
import { structure } from "../apiStructure/sammoRootAPI";
|
||||||
|
import { callClientAPI } from "./generator";
|
||||||
|
|
||||||
export type LoginResponse = {
|
export const SammoRootAPI = APIPathGen(structure, (path: string[], tail: APITail, pathParam) => {
|
||||||
result: true,
|
|
||||||
nextToken: [number, string] | undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LoginFailed = {
|
|
||||||
result: false,
|
|
||||||
reqOTP: boolean,
|
|
||||||
reason: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export type AutoLoginNonceResponse = {
|
|
||||||
result: true,
|
|
||||||
loginNonce: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AutoLoginResponse = {
|
|
||||||
result: true,
|
|
||||||
nextToken: [number, string] | undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export type AutoLoginFailed = {
|
|
||||||
result: false,
|
|
||||||
silent: boolean,
|
|
||||||
reason: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiRealPath = {
|
|
||||||
Login: {
|
|
||||||
LoginByID: POST as APICallT<{
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
}, LoginResponse, LoginFailed>,
|
|
||||||
LoginByToken: POST as APICallT<{
|
|
||||||
hashedToken: string,
|
|
||||||
token_id: number,
|
|
||||||
}, AutoLoginResponse, AutoLoginFailed>,
|
|
||||||
ReqNonce: GET as APICallT<undefined, AutoLoginNonceResponse, AutoLoginFailed>
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const SammoRootAPI = APIPathGen(apiRealPath, (path: string[], tail: APITail, pathParam) => {
|
|
||||||
const method = extractHttpMethod(tail);
|
const method = extractHttpMethod(tail);
|
||||||
return (args?: RawArgType, returnError?: boolean) => {
|
return (args?: RawArgType, returnError?: boolean) => {
|
||||||
if (returnError) {
|
if (returnError) {
|
||||||
|
|||||||
@@ -1,274 +0,0 @@
|
|||||||
import ky from "ky";
|
|
||||||
import { isArray, isEmpty } from "lodash-es";
|
|
||||||
|
|
||||||
export type ValidResponse = {
|
|
||||||
result: true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type APIRecoveryType = "login" | "2fa" | "gateway" | "game_login" | "game_quota";
|
|
||||||
export const APIRecoveryConst = {
|
|
||||||
Login: 'login',
|
|
||||||
TwoFactorAuth: '2fa',
|
|
||||||
Gateway: 'gateway',
|
|
||||||
GameLogin: 'game_login',
|
|
||||||
GameQuota: 'game_quota',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type InvalidResponse = {
|
|
||||||
result: false;
|
|
||||||
reason: string;
|
|
||||||
recovery?: APIRecoveryType;
|
|
||||||
recovery_arg?: string | number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RawArgType = Record<string, unknown> | Record<string, unknown>[] | undefined;
|
|
||||||
|
|
||||||
interface BasicAPICallT<
|
|
||||||
ArgType extends RawArgType,
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse
|
|
||||||
> {
|
|
||||||
(args: ArgType): Promise<ResultType>;
|
|
||||||
(args: ArgType, returnError: false): Promise<ResultType>;
|
|
||||||
(args: ArgType, returnError: true): Promise<ResultType | ErrorType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EmptyAPICallT<ResultType extends ValidResponse, ErrorType extends InvalidResponse> {
|
|
||||||
(): Promise<ResultType>;
|
|
||||||
(args: undefined): Promise<ResultType>;
|
|
||||||
(args: undefined, returnError: false): Promise<ResultType>;
|
|
||||||
(args: undefined, returnError: true): Promise<ResultType | ErrorType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export type ArgTypeOf<T> = T extends APICallT<infer A, any, any> ? A : never;
|
|
||||||
|
|
||||||
export type APICallT<
|
|
||||||
ArgType extends RawArgType,
|
|
||||||
ResultType extends ValidResponse = ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse = InvalidResponse
|
|
||||||
> = ArgType extends undefined ? EmptyAPICallT<ResultType, ErrorType> : BasicAPICallT<ArgType, ResultType, ErrorType>;
|
|
||||||
|
|
||||||
type HttpMethod = "get" | "post" | "put" | "patch" | "head" | "delete";
|
|
||||||
export type APITail = typeof GET | typeof POST | typeof PUT | typeof PATCH | typeof HEAD | typeof DELETE;
|
|
||||||
|
|
||||||
const httpMethodMap = new Map<APITail, HttpMethod>([
|
|
||||||
[GET, "get"],
|
|
||||||
[POST, "post"],
|
|
||||||
[PUT, "put"],
|
|
||||||
[PATCH, "patch"],
|
|
||||||
[HEAD, "head"],
|
|
||||||
[DELETE, "delete"],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function extractHttpMethod(tail: APITail): HttpMethod {
|
|
||||||
return httpMethodMap.get(tail) ?? "post";
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiTarget = "api.php";
|
|
||||||
let apiPath = apiTarget;
|
|
||||||
|
|
||||||
export function setSammoAPIPrefix(prefix: string) {
|
|
||||||
apiPath = `${prefix}/${apiTarget}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function callSammoAPI<ResultType extends ValidResponse>(
|
|
||||||
method: HttpMethod,
|
|
||||||
path: string | string[],
|
|
||||||
args: RawArgType,
|
|
||||||
paramArgs: Record<string, string | number> | undefined
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function callSammoAPI<ResultType extends ValidResponse>(
|
|
||||||
method: HttpMethod,
|
|
||||||
path: string | string[],
|
|
||||||
args: RawArgType,
|
|
||||||
paramArgs: Record<string, string | number> | undefined,
|
|
||||||
returnError: false
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function callSammoAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(
|
|
||||||
method: HttpMethod,
|
|
||||||
path: string | string[],
|
|
||||||
args: RawArgType,
|
|
||||||
paramArgs: Record<string, string | number> | undefined,
|
|
||||||
returnError: true
|
|
||||||
): Promise<ResultType | ErrorType>;
|
|
||||||
export async function callSammoAPI<ResultType extends ValidResponse, ErrorType extends InvalidResponse>(
|
|
||||||
method: HttpMethod,
|
|
||||||
path: string | string[],
|
|
||||||
args: RawArgType,
|
|
||||||
paramArgs: Record<string, string | number> | undefined,
|
|
||||||
returnError = false
|
|
||||||
): Promise<ResultType | ErrorType> {
|
|
||||||
if (isArray(path)) {
|
|
||||||
path = path.join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args && isEmpty(args)) {
|
|
||||||
args = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = (await (() => {
|
|
||||||
if (method == "get") {
|
|
||||||
return ky(apiPath, {
|
|
||||||
searchParams: {
|
|
||||||
...paramArgs,
|
|
||||||
...(args as typeof paramArgs),
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
method,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
timeout: 30000,
|
|
||||||
retry: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return ky("api.php", {
|
|
||||||
searchParams: {
|
|
||||||
...paramArgs,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
method,
|
|
||||||
json: args,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
timeout: 30000,
|
|
||||||
retry: 0,
|
|
||||||
});
|
|
||||||
})().json()) as ErrorType | ResultType;
|
|
||||||
|
|
||||||
if (!result.result) {
|
|
||||||
if (returnError) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
throw result.reason;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET<ResultType extends ValidResponse, ArgType extends undefined = undefined>(
|
|
||||||
args?: ArgType
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function GET<ResultType extends ValidResponse, ArgType extends undefined = undefined>(
|
|
||||||
args: ArgType | undefined,
|
|
||||||
returnError: false
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function GET<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends undefined = undefined
|
|
||||||
>(args: ArgType | undefined, returnError: true): Promise<ResultType | ErrorType>;
|
|
||||||
export async function GET<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends undefined = undefined
|
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
|
||||||
console.error(`Can't directly call GET. ${args}, ${returnError}. Use auto-generated path API.`);
|
|
||||||
return callSammoAPI<ResultType, ErrorType>("get", [], args, undefined, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
|
||||||
args?: ArgType
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function POST<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
|
||||||
args: ArgType | undefined,
|
|
||||||
returnError: false
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function POST<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends RawArgType = RawArgType
|
|
||||||
>(args: ArgType | undefined, returnError: true): Promise<ResultType | ErrorType>;
|
|
||||||
export async function POST<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends RawArgType = RawArgType
|
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
|
||||||
console.error(`Can't directly call POST. ${args}, ${returnError}. Use auto-generated path API.`);
|
|
||||||
return callSammoAPI<ResultType, ErrorType>("post", [], args, undefined, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function PUT<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
|
||||||
args?: ArgType
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function PUT<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
|
||||||
args: ArgType | undefined,
|
|
||||||
returnError: false
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function PUT<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends RawArgType = RawArgType
|
|
||||||
>(args: ArgType | undefined, returnError: true): Promise<ResultType | ErrorType>;
|
|
||||||
export async function PUT<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends RawArgType = RawArgType
|
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
|
||||||
console.error(`Can't directly call PUT. ${args}, ${returnError}. Use auto-generated path API.`);
|
|
||||||
return callSammoAPI<ResultType, ErrorType>("put", [], args, undefined, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function PATCH<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
|
||||||
args?: ArgType
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function PATCH<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
|
||||||
args: ArgType | undefined,
|
|
||||||
returnError: false
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function PATCH<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends RawArgType = RawArgType
|
|
||||||
>(args: ArgType | undefined, returnError: true): Promise<ResultType | ErrorType>;
|
|
||||||
export async function PATCH<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends RawArgType = RawArgType
|
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
|
||||||
console.error(`Can't directly call PATCH. ${args}, ${returnError}. Use auto-generated path API.`);
|
|
||||||
return callSammoAPI<ResultType, ErrorType>("patch", [], args, undefined, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function HEAD<ResultType extends ValidResponse, ArgType extends undefined = undefined>(
|
|
||||||
args?: ArgType
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function HEAD<ResultType extends ValidResponse, ArgType extends undefined = undefined>(
|
|
||||||
args: ArgType | undefined,
|
|
||||||
returnError: false
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function HEAD<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends undefined = undefined
|
|
||||||
>(args: ArgType | undefined, returnError: true): Promise<ResultType | ErrorType>;
|
|
||||||
export async function HEAD<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends undefined = undefined
|
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
|
||||||
console.error(`Can't directly call HEAD. ${args}, ${returnError}. Use auto-generated path API.`);
|
|
||||||
return callSammoAPI<ResultType, ErrorType>("head", [], args, undefined, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function DELETE<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
|
||||||
args?: ArgType
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function DELETE<ResultType extends ValidResponse, ArgType extends RawArgType = RawArgType>(
|
|
||||||
args: ArgType | undefined,
|
|
||||||
returnError: false
|
|
||||||
): Promise<ResultType>;
|
|
||||||
export async function DELETE<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends RawArgType = RawArgType
|
|
||||||
>(args: ArgType | undefined, returnError: true): Promise<ResultType | ErrorType>;
|
|
||||||
export async function DELETE<
|
|
||||||
ResultType extends ValidResponse,
|
|
||||||
ErrorType extends InvalidResponse,
|
|
||||||
ArgType extends RawArgType = RawArgType
|
|
||||||
>(args?: ArgType, returnError = false): Promise<ResultType | ErrorType> {
|
|
||||||
console.error(`Can't directly call DELETE. ${args}, ${returnError}. Use auto-generated path API.`);
|
|
||||||
return callSammoAPI<ResultType, ErrorType>("patch", [], args, undefined, true);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user