First commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import SetupCreateAdmin from "./setup/create-admin";
|
||||
import SetupInitilization from "./setup/initialization";
|
||||
|
||||
type ApiMap =
|
||||
SetupInitilization &
|
||||
SetupCreateAdmin;
|
||||
|
||||
export default ApiMap;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { InputError, InputNoneError } from "../../modules/error/input";
|
||||
import ErrorBase from "../../modules/error";
|
||||
import DatabaseError from "../../modules/error/database";
|
||||
|
||||
export default interface SetupCreateAdmin {
|
||||
"setup/create-admin": {
|
||||
body: {
|
||||
userid: string;
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
response: Success | DatabaseError | ErrorBase<{
|
||||
bad: "client",
|
||||
code: "yet_initialization",
|
||||
message: "初期設定が行われていません。",
|
||||
}> | ErrorBase<{
|
||||
bad: "client",
|
||||
code: "first_admin_already_exists",
|
||||
message: "最初の管理者ユーザーは既に存在します。",
|
||||
}> | InputError | InputNoneError;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { InputError, InputNoneError } from "../../modules/error/input";
|
||||
import ErrorBase from "../../modules/error";
|
||||
import DatabaseError from "../../modules/error/database";
|
||||
|
||||
export default interface SetupInitilization {
|
||||
"setup/initilization": {
|
||||
body: {
|
||||
name: string;
|
||||
description: string;
|
||||
requiredInvitationCode: boolean;
|
||||
force?: "use_force_initialization";
|
||||
};
|
||||
response: Success | DatabaseError | ErrorBase<{
|
||||
bad: "client",
|
||||
code: "already_initialization",
|
||||
message: "既に初期設定が行われています。",
|
||||
}> | InputError | InputNoneError;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import ErrorBase from ".";
|
||||
|
||||
type DatabaseError = ErrorBase<{
|
||||
bad: "server",
|
||||
code: "database_error",
|
||||
message: "サーバーでデータベースの問題が発生しました。",
|
||||
}>;
|
||||
|
||||
export default DatabaseError;
|
||||
@@ -0,0 +1,13 @@
|
||||
type ErrorType<R = unknown> = {
|
||||
bad: "client" | "server";
|
||||
code: string;
|
||||
message: string;
|
||||
reason?: R,
|
||||
}
|
||||
|
||||
type ErrorBase<E extends ErrorType<any>> = {
|
||||
success: false;
|
||||
error: E;
|
||||
}
|
||||
|
||||
export default ErrorBase;
|
||||
@@ -0,0 +1,16 @@
|
||||
export type InputError = ErrorBase<{
|
||||
bad: "client";
|
||||
code: "input_wrong";
|
||||
message: "入力に問題があります。";
|
||||
reason: {
|
||||
code: string,
|
||||
path: string,
|
||||
message: string,
|
||||
}[];
|
||||
}>;
|
||||
|
||||
export type InputNoneError = ErrorBase<{
|
||||
bad: "client",
|
||||
code: "input_none",
|
||||
message: "入力がありません。",
|
||||
}>;
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface Success {
|
||||
success: true;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import lynqError from "./lib/error";
|
||||
import lynqFetch from "./lib/fetch";
|
||||
|
||||
interface Options {
|
||||
origin: string;
|
||||
retry?: number;
|
||||
waiting?: number;
|
||||
}
|
||||
|
||||
type BodyArgs<M, E extends keyof M> =
|
||||
M[E] extends { body: never } ? [] :
|
||||
M[E] extends { body: infer B } ? [body: B] :
|
||||
M[E] extends { body?: infer B } ? [body?: B] :
|
||||
[];
|
||||
|
||||
export default class LynqChat<
|
||||
M extends { [K in keyof M]: { body?: any; response: any } }
|
||||
> {
|
||||
readonly origin: string;
|
||||
readonly retry: number;
|
||||
readonly waiting: number;
|
||||
private _token: string | null = null;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.origin = options.origin;
|
||||
this.retry = options.retry ?? 5;
|
||||
this.waiting = options.waiting ?? 500;
|
||||
|
||||
if (this.retry < 1) throw new lynqError("Invalid retry count.");
|
||||
if (this.waiting < 1) throw new lynqError("Invalid base waiting time.");
|
||||
if (options.origin !== new URL(options.origin).origin)
|
||||
throw new lynqError("Invalid origin.");
|
||||
}
|
||||
|
||||
get token(): string | null {
|
||||
return this._token;
|
||||
}
|
||||
|
||||
set token(token: string) {
|
||||
if (token.length !== 64) throw new lynqError("Invalid token.");
|
||||
this._token = token;
|
||||
}
|
||||
|
||||
public async request<E extends keyof M>(
|
||||
endpoint: E,
|
||||
...args: BodyArgs<M, E>
|
||||
): Promise<M[E]["response"]>;
|
||||
public async request(
|
||||
endpoint: string,
|
||||
body?: any
|
||||
): Promise<any>;
|
||||
public async request(
|
||||
endpoint: string,
|
||||
...args: any[]
|
||||
): Promise<any> {
|
||||
|
||||
const req = await lynqFetch(
|
||||
this.origin,
|
||||
this.retry,
|
||||
this.waiting,
|
||||
endpoint,
|
||||
{
|
||||
method: "POST",
|
||||
cache: "no-store",
|
||||
body: args[0],
|
||||
}
|
||||
);
|
||||
|
||||
return await req.json();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default class lynqError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "lynqError";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import lynqError from "./error";
|
||||
|
||||
export default async function lynqFetch(
|
||||
origin: string,
|
||||
retryCount: number,
|
||||
waitingTime: number,
|
||||
endpoint: string,
|
||||
init?: RequestInit,
|
||||
) {
|
||||
const waiting = (ms: number) =>
|
||||
new Promise<void>(resolve => setTimeout(resolve, ms));
|
||||
|
||||
let lastError;
|
||||
|
||||
for (let i = 0; i < retryCount; i++) {
|
||||
try {
|
||||
if (init?.signal?.aborted) {
|
||||
throw new DOMException("Aborted", "AbortError");
|
||||
}
|
||||
|
||||
const req = await fetch(new URL(`/api/${endpoint}`, origin), init);
|
||||
|
||||
if (
|
||||
Math.floor(req.status / 200) === 1 ||
|
||||
Math.floor(req.status / 300) === 1
|
||||
) {
|
||||
return req;
|
||||
}
|
||||
|
||||
if (Math.floor(req.status / 400) === 1)
|
||||
throw new lynqError(`Client error: HTTP${req.status} - ${req.statusText}`);
|
||||
|
||||
lastError = new Error(`Request failed: HTTP${req.status} - ${req.statusText}`);
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException && err.name === "AbortError") {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (
|
||||
err instanceof lynqError &&
|
||||
err.name === "Client error" &&
|
||||
err.message.startsWith("HTTP4")
|
||||
)
|
||||
throw err;
|
||||
|
||||
lastError = err;
|
||||
}
|
||||
|
||||
if (i < retryCount - 1) {
|
||||
const waitTime = waitingTime * 2 ** i;
|
||||
await waiting(waitTime);
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError ?? new Error("Unknown Error");
|
||||
}
|
||||
Reference in New Issue
Block a user