wip
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
import type { RNG } from "../../../server/util/RNG.js";
|
||||
|
||||
import { sha512 } from '../../../server/util/sha2.js';
|
||||
|
||||
import { convertBytesLikeToUint8Array } from "@sammo/util/convertBytesLikeToUint8Array.js";
|
||||
import type { BytesLike } from "../../../server/util/BytesLike.js";
|
||||
import { delay } from "./delay.js";
|
||||
import type { RNG } from "./RNG.js";
|
||||
import { sha512 } from './SHA2.js';
|
||||
import { convertBytesLikeToUint8Array } from "@sammo/util/converter";
|
||||
import type { BytesLike } from "@sammo/util";
|
||||
import { delay } from "@sammo/util";
|
||||
|
||||
const maxRngSupportBit = 53;
|
||||
const maxInt = 0x1f_ffff_ffff_ffff; // NOTE: b 0, 10000110011, 11...11
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./exports": "./dist/exports.js"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { structure } from "../apiStructure/sammoGatewayAPI.js";
|
||||
import type { APINamespaceType } from "./defs.js";
|
||||
import { GetGameLoginToken } from "./GatewayAPI/GetGameLoginToken.js";
|
||||
import { Login } from "./GatewayAPI/Login/index.js";
|
||||
|
||||
export const sammoGatewayAPI = {
|
||||
Login,
|
||||
GetGameLoginToken
|
||||
} satisfies APINamespaceType<typeof structure>;
|
||||
@@ -0,0 +1,9 @@
|
||||
export type {
|
||||
ServerActionType,
|
||||
UserIDType,
|
||||
IUser,
|
||||
} from "./schema/User.ts";
|
||||
|
||||
export type {
|
||||
ILoginToken
|
||||
} from "./schema/LoginToken.ts";
|
||||
@@ -17,7 +17,7 @@ const validGatewayActionTypeList = [
|
||||
|
||||
export type GatewayActionType = typeof validGatewayActionTypeList[number];
|
||||
|
||||
interface IUser {
|
||||
export interface IUser {
|
||||
_id: UserIDType;
|
||||
|
||||
oauthID?: bigint;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@sammo/server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/api_def": "workspace:^",
|
||||
"@sammo/server_util": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^",
|
||||
"dotenv": "^16.3.1",
|
||||
"mongoose": "^7.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.6.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../../@strpc/express"
|
||||
},
|
||||
{
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../crypto"
|
||||
},
|
||||
{
|
||||
"path": "../server_util"
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -8,9 +8,11 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./converter": "./dist/converter.js",
|
||||
"./datetime": "./dist/datetime.js",
|
||||
"./korean": "./dist/korean.js"
|
||||
"./converter": "./dist/converter/index.js",
|
||||
"./datetime": "./dist/datetime/index.js",
|
||||
"./korean": "./dist/korean/index.js",
|
||||
"./josa_util": "./dist/korean/josa_util.js",
|
||||
"./string": "./dist/string/index.js"
|
||||
},
|
||||
"type": "module",
|
||||
"keywords": [],
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { BytesLike } from "./BytesLike.js";
|
||||
|
||||
export function convertBytesLikeToArrayBuffer(data: BytesLike, encodeUTF8 = true): ArrayBuffer{
|
||||
if (data instanceof ArrayBuffer) {
|
||||
return data;
|
||||
}
|
||||
if (data instanceof Uint8Array) {
|
||||
return data.buffer;
|
||||
}
|
||||
if (typeof(data) === 'string'){
|
||||
if(encodeUTF8){
|
||||
return (new TextEncoder()).encode(data);
|
||||
}
|
||||
return new Uint8Array(data.split('').map(s=>s.codePointAt(0) as number));
|
||||
}
|
||||
return data.buffer;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { BytesLike } from "./BytesLike.js";
|
||||
import type { BytesLike, BufferSource } from "../types.js";
|
||||
|
||||
export function convertBytesLikeToUint8Array(data: string, encodeUTF8: boolean): Uint8Array;
|
||||
export function convertBytesLikeToUint8Array(data: BufferSource | string): Uint8Array;
|
||||
export function convertBytesLikeToUint8Array(data: BytesLike, encodeUTF8 = true): Uint8Array {
|
||||
if (data instanceof Uint8Array) {
|
||||
return data;
|
||||
@@ -7,11 +9,20 @@ export function convertBytesLikeToUint8Array(data: BytesLike, encodeUTF8 = true)
|
||||
if (data instanceof ArrayBuffer) {
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
if (data instanceof SharedArrayBuffer) {
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
if (data instanceof Uint8Array) {
|
||||
return data;
|
||||
}
|
||||
if (ArrayBuffer.isView(data)) {
|
||||
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||
}
|
||||
if (typeof (data) === 'string') {
|
||||
if(encodeUTF8){
|
||||
return (new TextEncoder()).encode(data);
|
||||
}
|
||||
return new Uint8Array(data.split('').map(s=>s.codePointAt(0) as number));
|
||||
}
|
||||
return new Uint8Array(data.buffer);
|
||||
throw new Error(`Unknown data type ${typeof (data)}`);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from "./combineObject.js";
|
||||
export * from "./combineArray.js";
|
||||
export * from "./convertBytesLikeToArrayBuffer.js";
|
||||
export * from "./convertBytesLikeToUint8Array.js";
|
||||
export * from "./convertIDArray.js";
|
||||
export * from "./convertIterableToMap.js"
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
|
||||
import type { ValuesOf } from "./defs.js";
|
||||
import type { ValuesOf } from "../types.js";
|
||||
import { zip } from "lodash-es";
|
||||
|
||||
export function merge2DArrToObjectArr<T extends Record<string, unknown>>(column: (keyof T)[], list: ValuesOf<T>[][]): T[]{
|
||||
+10
-54
@@ -3,58 +3,19 @@ export * from "./jsonify.js"
|
||||
export * from "./error.js"
|
||||
export * from "./unwrap.js"
|
||||
export * from "./types.js"
|
||||
export * from "./web.js"
|
||||
|
||||
export * from "./strongType.js"
|
||||
|
||||
export * as converter from "./converter/index.js"
|
||||
export * as datetime from "./datetime/index.js"
|
||||
export * as korean from "./korean/index.js"
|
||||
|
||||
import type { WrappedBuffer } from "./types.js"
|
||||
|
||||
export function isEmail(addr: string): boolean {
|
||||
return String(addr)
|
||||
.toLowerCase()
|
||||
.match(
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
) !== null;
|
||||
}
|
||||
|
||||
export function isIPAddr4(addr: string): boolean {
|
||||
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(addr)) {
|
||||
return (true)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export * as datetime from "./datetime/index.js";
|
||||
export * as korean from "./korean/index.js";
|
||||
export * as string from "./string/index.js";
|
||||
|
||||
export function calcBase64Len(length: number) {
|
||||
return ((4 * length / 3) + 3) & ~3;
|
||||
}
|
||||
|
||||
/** ArrayBuffer,Uint8Array,Buffer를 WrappedBuffer로 변환
|
||||
*/
|
||||
export function wrapBuffer<T extends WrappedBuffer>(buffer: BufferSource): T {
|
||||
/**
|
||||
* Buffer라면 wrapBuffer없이 그대로 대입 가능
|
||||
* ArrayBuffer, Uint8Array 등이라면 Buffer.from을 사용 가능
|
||||
* DataView 등인 경우는 ArrayBufferView 활용
|
||||
* 그냥 써도 되지만, 위 방법보다 아무렇게나 쓰기 좋다는 장점은 있음
|
||||
*/
|
||||
if (buffer instanceof Buffer) {
|
||||
return buffer as T;
|
||||
}
|
||||
if (buffer instanceof ArrayBuffer) {
|
||||
return Buffer.from(buffer) as T;
|
||||
}
|
||||
if (buffer instanceof SharedArrayBuffer) {
|
||||
return Buffer.from(buffer) as T;
|
||||
}
|
||||
return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength) as T;
|
||||
}
|
||||
|
||||
/** 호출할 필요 없이 바로 Buffer에 대입해도 되지만, 누군가 찾을 수 있어서. */
|
||||
export function unwrapBuffer<T extends WrappedBuffer>(buffer: T): Buffer {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function delay(time: number): Promise<void>;
|
||||
export function delay<T>(time: number, result: T): Promise<T>;
|
||||
export function delay<T = undefined>(time: number, result?: T): Promise<T | void> {
|
||||
@@ -65,15 +26,6 @@ export function delay<T = undefined>(time: number, result?: T): Promise<T | void
|
||||
);
|
||||
}
|
||||
|
||||
export function base64FromWrappedBuffer<T extends WrappedBuffer>(buffer: T | ArrayBuffer | Uint8Array): Base64String<T> {
|
||||
return Buffer.from(buffer).toString('base64');
|
||||
}
|
||||
|
||||
export function wrappedBufferFromBase64<T extends WrappedBuffer>(base64: Base64String<T>): T {
|
||||
return Buffer.from(base64, 'base64') as T;
|
||||
}
|
||||
|
||||
|
||||
export function isBufferSource(obj: unknown): obj is BufferSource {
|
||||
if (obj instanceof ArrayBuffer){
|
||||
return true;
|
||||
@@ -85,4 +37,8 @@ export function isBufferSource(obj: unknown): obj is BufferSource {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isNotNull<T>(value: T|null|undefined):value is T{
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { unwrap_err } from "./unwrap_err.js";
|
||||
import { unwrap_err } from "../unwrap.js";
|
||||
|
||||
// https://github.com/coxcore/postposition 의 php 버전을 다시 typescript로 재 작성
|
||||
const KO_START_CODE = 44032;
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./automata초성.js";
|
||||
export * from "./convertSearch초성.js"
|
||||
export * from "./filter초성.js";
|
||||
export * from "./filter초성withAlphabet.js";
|
||||
export * from "./filter초성withAlphabet.js";
|
||||
export * as JosaUtil from "./JosaUtil.js";
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./mb_strimwidth.js"
|
||||
export * from "./mb_strwidth.js"
|
||||
export * from "./nl2br.js"
|
||||
export * from "./numberWithCommas.js"
|
||||
export * from "./randStr.js";
|
||||
@@ -3,10 +3,10 @@ const charList = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
|
||||
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z',
|
||||
];
|
||||
] as const;
|
||||
|
||||
export function randStr(len: number): string {
|
||||
const result = [];
|
||||
const result: string[] = [];
|
||||
const charListLen = charList.length;
|
||||
let isStart = true;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
|
||||
/** Buffer이지만 ts에서 타입 구분 편의를 제공 */
|
||||
export interface WrappedBuffer extends Buffer {
|
||||
/** 타입구분자. 항상 undefined일 것이다 */
|
||||
_w_type?: string;
|
||||
}
|
||||
|
||||
|
||||
/** ArrayBuffer,Uint8Array,Buffer를 WrappedBuffer로 변환
|
||||
*/
|
||||
export function wrapBuffer<T extends WrappedBuffer>(buffer: BufferSource): T {
|
||||
/**
|
||||
* Buffer라면 wrapBuffer없이 그대로 대입 가능
|
||||
* ArrayBuffer, Uint8Array 등이라면 Buffer.from을 사용 가능
|
||||
* DataView 등인 경우는 ArrayBufferView 활용
|
||||
* 그냥 써도 되지만, 위 방법보다 아무렇게나 쓰기 좋다는 장점은 있음
|
||||
*/
|
||||
if (buffer instanceof Buffer) {
|
||||
return buffer as T;
|
||||
}
|
||||
if (buffer instanceof ArrayBuffer) {
|
||||
return Buffer.from(buffer) as T;
|
||||
}
|
||||
if (buffer instanceof SharedArrayBuffer) {
|
||||
return Buffer.from(buffer) as T;
|
||||
}
|
||||
return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength) as T;
|
||||
}
|
||||
|
||||
/** 호출할 필요 없이 바로 Buffer에 대입해도 되지만, 누군가 찾을 수 있어서. */
|
||||
export function unwrapBuffer<T extends WrappedBuffer>(buffer: T): Buffer {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* base64 string에 내부 타입으로 WrappedBuffer를 보관한 형태
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type Base64String<T extends Buffer> = string & {
|
||||
/** 타입구분자. 항상 undefined일 것이다 */
|
||||
_b_type?: T;
|
||||
}
|
||||
|
||||
export function base64FromWrappedBuffer<T extends WrappedBuffer>(buffer: T | ArrayBuffer | Uint8Array): Base64String<T> {
|
||||
return Buffer.from(buffer).toString('base64');
|
||||
}
|
||||
|
||||
export function wrappedBufferFromBase64<T extends WrappedBuffer>(base64: Base64String<T>): T {
|
||||
return Buffer.from(base64, 'base64') as T;
|
||||
}
|
||||
@@ -6,23 +6,10 @@ export type IDItem<T> = {
|
||||
id: T;
|
||||
};
|
||||
|
||||
export type Entries<T> = {
|
||||
[K in keyof T]: [K, T[K]];
|
||||
}[keyof T][];
|
||||
|
||||
|
||||
/** Buffer이지만 ts에서 타입 구분 편의를 제공 */
|
||||
export interface WrappedBuffer extends Buffer {
|
||||
/** 타입구분자. 항상 undefined일 것이다 */
|
||||
_w_type?: string;
|
||||
}
|
||||
|
||||
export type BufferSource = ArrayBufferView | ArrayBuffer | SharedArrayBuffer;
|
||||
export type BytesLike = BufferSource | string;
|
||||
|
||||
|
||||
/**
|
||||
* base64 string에 내부 타입으로 WrappedBuffer를 보관한 형태
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type Base64String<T extends Buffer> = string & {
|
||||
/** 타입구분자. 항상 undefined일 것이다 */
|
||||
_b_type?: T;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { unwrap } from "./unwrap.js";
|
||||
|
||||
export function isEmail(addr: string): boolean {
|
||||
return String(addr)
|
||||
.toLowerCase()
|
||||
.match(
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
) !== null;
|
||||
}
|
||||
|
||||
export function isIPAddr4(addr: string): boolean {
|
||||
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(addr)) {
|
||||
return (true)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hexToRgb(hex: string): { r: number; g: number; b: number; } | null {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
}
|
||||
|
||||
export function isBrightColor(color: string): boolean {
|
||||
const cv = unwrap(hexToRgb(color));
|
||||
if ((cv.r * 0.299 + cv.g * 0.587 + cv.b * 0.114) > 140) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { GatewayActionType, ServerActionType } from "../schema/gateway/User.js";
|
||||
import type { SessionCtx } from "./StartSession.js";
|
||||
import type { ProcDecorator } from "./base.js";
|
||||
|
||||
export type GatewayLoginCtx = {
|
||||
userID: number;
|
||||
userName: string;
|
||||
userLevel: number;
|
||||
allowServerAction: Map<string, Set<ServerActionType>>;
|
||||
allowGatewayAction: Set<GatewayActionType>;
|
||||
loginDate: Date;
|
||||
}
|
||||
export const loginCtxSessionKey = 'loginCtx';
|
||||
|
||||
export function ReqGatewayLogin<Q extends SessionCtx>(): ProcDecorator<GatewayLoginCtx & Q, Q> {
|
||||
return (ctx) => {
|
||||
const loginCtx = ctx.session.getItem<GatewayLoginCtx>(loginCtxSessionKey);
|
||||
if(!loginCtx){
|
||||
return [{
|
||||
result: false,
|
||||
type: 'Required Login',
|
||||
info: 'ReqLogin'
|
||||
}, ctx];
|
||||
}
|
||||
|
||||
return [{
|
||||
result: true,
|
||||
},{
|
||||
...loginCtx,
|
||||
...ctx,
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { type Session, type SessionData } from "express-session";
|
||||
import type { Empty, ProcDecoratorGenerator } from "./base.js";
|
||||
|
||||
export type SessionCtx = {
|
||||
session: {
|
||||
clear: () => Promise<void>;
|
||||
removeItem: (key: string) => boolean;
|
||||
getItem: <T>(key: string) => T | undefined;
|
||||
setItem: (key: string, value: unknown) => void;
|
||||
raw: Session & Partial<SessionData> & Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
|
||||
export function StartSession<Q extends object = Empty>(): ProcDecoratorGenerator<SessionCtx, Q> {
|
||||
return (inCtx, req) => {
|
||||
if (!req.session) {
|
||||
throw 'Express-session required';
|
||||
}
|
||||
const sessionObj = {
|
||||
raw: req.session
|
||||
} as SessionCtx['session'];
|
||||
sessionObj.clear = () => {
|
||||
return new Promise((resolve) => {
|
||||
sessionObj.raw = req.session.regenerate(resolve) as SessionCtx['session']['raw'];
|
||||
})
|
||||
}
|
||||
sessionObj.removeItem = (key: string): boolean => {
|
||||
if (key in sessionObj.raw) {
|
||||
delete sessionObj.raw[key];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
sessionObj.getItem = <T>(key: string): T | undefined => {
|
||||
if (!(key in sessionObj.raw)) {
|
||||
return undefined;
|
||||
}
|
||||
return sessionObj.raw[key] as T;
|
||||
}
|
||||
sessionObj.setItem = <T>(key: string, value: T | undefined): void => {
|
||||
if (value === undefined) {
|
||||
sessionObj.removeItem(key);
|
||||
return;
|
||||
}
|
||||
sessionObj.raw[key] = value;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
session: sessionObj,
|
||||
...inCtx,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
type MayBePromise<T> = T | Promise<T>;
|
||||
|
||||
export type Empty = Record<string, never>;
|
||||
export type DecoratorResultTrue = {
|
||||
result: true;
|
||||
type?: string;
|
||||
info?: string;
|
||||
};
|
||||
export type DecoratorResultFalse = {
|
||||
result: false;
|
||||
type: string;
|
||||
info: string;
|
||||
}
|
||||
export type DecoratorResult = DecoratorResultTrue | DecoratorResultFalse;
|
||||
export type DecoratorStack = DecoratorResult[];
|
||||
|
||||
export interface ProcDecorator<Out extends object, In = Empty> {
|
||||
(inCtx: In & Partial<Out>, req: Request, res: Response)
|
||||
: MayBePromise<[DecoratorResultTrue, Out] | [DecoratorResultFalse, In & Partial<Out>]>;
|
||||
}
|
||||
|
||||
export interface PostProcDecorator<T extends object> {
|
||||
(ctx: T, preResult: DecoratorResult, req: Request, res: Response, isValidRoute: boolean): MayBePromise<[DecoratorResult, T]>;
|
||||
}
|
||||
|
||||
export interface ProcDecoratorRunner<Out extends object, In> {
|
||||
(inCtx: In, req: Request, res: Response): MayBePromise<[DecoratorStack, Out]>;
|
||||
}
|
||||
|
||||
export interface PostProcDecoratorRunner<T extends object> {
|
||||
(ctx: T, preResult: DecoratorStack, req: Request, res: Response, isValidRoute: boolean): MayBePromise<[DecoratorStack, T]>;
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type PlainDecorator = ProcDecorator<any, any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type PlainPostDecorator = PostProcDecorator<any>;
|
||||
|
||||
|
||||
export type ProcDecoratorGenerator<Out extends object, In = Empty> = ProcDecorator<Out & In, In>;
|
||||
export type ProcDecoratorPrePostGenerator<Out extends object, In> = [ProcDecorator<Out & In, In>, PostProcDecorator<Out & In>];
|
||||
|
||||
export type ProcDecoratorChain = readonly ((() => PlainDecorator) | (() => [PlainDecorator, PlainPostDecorator]))[];
|
||||
|
||||
export type ResolveChain<T> = T extends undefined ? Empty : T extends ProcDecoratorChain ? Resolve<PackChain<T>> : never;
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type ParseInType<T> = T extends ProcDecorator<any, infer A> ? object extends A ? A : never : never;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type ParseOutType<T> = T extends ProcDecorator<infer B, object> ? B : never;
|
||||
|
||||
export const EmptyProcDecorator: readonly [ProcDecoratorRunner<Empty, Empty>, PostProcDecoratorRunner<Empty>] = [
|
||||
async (ctx) => [[], ctx], async (ctx, stack) => [stack, ctx]
|
||||
];
|
||||
|
||||
export function declProcDecorators<T extends ProcDecoratorChain>(...decorators: T) {
|
||||
type OutType = Resolve<PackChain<T>>;
|
||||
|
||||
const preDecorator: PlainDecorator[] = [];
|
||||
const postDecorator: (PlainPostDecorator | undefined)[] = [];
|
||||
if (decorators) {
|
||||
for (const procGen of decorators) {
|
||||
const proc = procGen();
|
||||
if (Array.isArray(proc)) {
|
||||
preDecorator.push(proc[0]);
|
||||
postDecorator.push(proc[1]);
|
||||
}
|
||||
else {
|
||||
preDecorator.push(proc);
|
||||
postDecorator.push(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const packedDecorators: readonly [ProcDecoratorRunner<OutType, Empty>, PostProcDecoratorRunner<OutType>] = [
|
||||
async (ctx, req, res) => {
|
||||
let rctx = ctx as unknown as OutType;
|
||||
const decoratorStack: DecoratorStack = [];
|
||||
if (!preDecorator.length) {
|
||||
return [decoratorStack, rctx];
|
||||
}
|
||||
|
||||
for (const [idx, proc] of preDecorator.entries()) {
|
||||
try {
|
||||
const [stackResult, newCtx] = await proc(rctx, req, res);
|
||||
decoratorStack.push(stackResult);
|
||||
|
||||
if (stackResult.result) {
|
||||
rctx = newCtx;
|
||||
continue;
|
||||
}
|
||||
|
||||
return [decoratorStack, newCtx];
|
||||
}
|
||||
catch (e) {
|
||||
while (decoratorStack.length > idx) {
|
||||
decoratorStack.pop();
|
||||
}
|
||||
decoratorStack.push({
|
||||
result: false,
|
||||
type: 'PreThrow',
|
||||
info: `internal error: ${e}`,
|
||||
});
|
||||
|
||||
return [decoratorStack, rctx];
|
||||
}
|
||||
}
|
||||
|
||||
return [decoratorStack, rctx];
|
||||
},
|
||||
async (ctx, stack, req, res) => {
|
||||
if (!postDecorator.length) {
|
||||
return [stack, ctx];
|
||||
}
|
||||
let isValidRoute = stack.length === postDecorator.length && stack.every(v => v.result);
|
||||
for (let idx = stack.length - 1; idx >= 0; idx--) {
|
||||
const preStackResult = stack[idx] as DecoratorResult;
|
||||
if (!preStackResult.result) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const proc = postDecorator[idx];
|
||||
|
||||
if (!proc) {
|
||||
if (isValidRoute) {
|
||||
stack.pop();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const [postStackResult, nextCtx] = await proc(ctx, preStackResult, req, res, isValidRoute);
|
||||
if (postStackResult.result) {
|
||||
if (isValidRoute) {
|
||||
stack.pop();
|
||||
}
|
||||
ctx = nextCtx;
|
||||
continue;
|
||||
}
|
||||
|
||||
isValidRoute = false;
|
||||
stack[idx] = postStackResult;
|
||||
|
||||
ctx = nextCtx;
|
||||
}
|
||||
catch (e) {
|
||||
isValidRoute = false;
|
||||
stack[idx] = {
|
||||
result: false,
|
||||
type: 'PostThrow',
|
||||
info: `internal error: ${e}`,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
return [stack, ctx];
|
||||
},
|
||||
] as const;
|
||||
|
||||
return packedDecorators;
|
||||
}
|
||||
|
||||
type Compose2<D extends object, C, B, A = Empty> = B extends C ? ProcDecorator<D & B, A> : never;
|
||||
|
||||
type PD1<B extends object, A extends object> = () => ProcDecorator<B, A>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type PD2<B extends object, A extends object> = () => [ProcDecorator<B, A>, PostProcDecorator<any>];
|
||||
|
||||
export type PackChain<T> =
|
||||
T extends readonly [] ? ProcDecorator<Empty, Empty> :
|
||||
T extends readonly [PD1<infer B, infer A>] ? ProcDecorator<B, A> :
|
||||
T extends readonly [PD2<infer B, infer A>] ? ProcDecorator<B, A> :
|
||||
T extends readonly [PD1<infer B, infer A>, PD1<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<D, C, B, A>, ...R]> :
|
||||
T extends readonly [PD2<infer B, infer A>, PD1<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<D, C, B, A>, ...R]> :
|
||||
T extends readonly [PD1<infer B, infer A>, PD2<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<D, C, B, A>, ...R]> :
|
||||
T extends readonly [PD2<infer B, infer A>, PD2<infer D, infer C>, ... infer R] ? PackChain<[() => Compose2<D, C, B, A>, ...R]> :
|
||||
never;
|
||||
|
||||
type Resolve<T> = T extends ProcDecorator<infer B, infer A> ? Empty extends A ? B : never : never;
|
||||
@@ -1,38 +0,0 @@
|
||||
export function APIPathGen<T extends object, V>(
|
||||
obj: T,
|
||||
callback: (path: string[], tail: V, pathParam?: Record<string, string | number>) => unknown,
|
||||
path: string[] = [],
|
||||
): T {
|
||||
const map = new Map<string, unknown>();
|
||||
return new Proxy(obj, {
|
||||
get(target, key) {
|
||||
if(typeof key === 'symbol'){
|
||||
throw new Error('Symbol is not supported');
|
||||
}
|
||||
|
||||
const cachedResult = map.get(key);
|
||||
if(cachedResult !== undefined){
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
const nextPath = [...path, key];
|
||||
|
||||
let next: T[keyof T];
|
||||
if (key in target) {
|
||||
next = target[key as keyof typeof target];
|
||||
}
|
||||
else {
|
||||
throw `${nextPath} is not exists`;
|
||||
}
|
||||
|
||||
if (typeof (next) === 'function') {
|
||||
const result = callback(nextPath, next);
|
||||
map.set(key, result);
|
||||
return result;
|
||||
}
|
||||
const result = APIPathGen<T[keyof T], V>(next, callback, nextPath);
|
||||
map.set(key, result);
|
||||
return result;
|
||||
}
|
||||
}) as T;
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
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.js";
|
||||
|
||||
export async function callClientAPI<ResultType extends ValidResponse>(
|
||||
method: HttpMethod,
|
||||
apiRoot: string,
|
||||
path: string | string[],
|
||||
args: RawArgType,
|
||||
paramArgs: Record<string, string | number> | undefined
|
||||
): Promise<ResultType>;
|
||||
export async function callClientAPI<ResultType extends ValidResponse>(
|
||||
method: HttpMethod,
|
||||
apiRoot: string,
|
||||
path: string | string[],
|
||||
args: RawArgType,
|
||||
paramArgs: Record<string, string | number> | undefined,
|
||||
returnError: undefined
|
||||
): Promise<ResultType>;
|
||||
export async function callClientAPI<ResultType extends ValidResponse>(
|
||||
method: HttpMethod,
|
||||
apiRoot: string,
|
||||
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,
|
||||
apiRoot: string,
|
||||
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,
|
||||
apiRoot: string,
|
||||
path: string | string[],
|
||||
args: RawArgType,
|
||||
paramArgs: Record<string, string | number> | undefined,
|
||||
returnError?: boolean
|
||||
): Promise<ResultType | ErrorType> {
|
||||
if (isArray(path)) {
|
||||
path = [apiRoot, ...path].join("/");
|
||||
}
|
||||
else if (path.startsWith("/")) {
|
||||
path = `${apiRoot}${path}`;
|
||||
}
|
||||
else {
|
||||
path = `${apiRoot}/${path}`;
|
||||
}
|
||||
|
||||
if (args && isEmpty(args)) {
|
||||
args = undefined;
|
||||
}
|
||||
|
||||
const result = (await (() => {
|
||||
if (method == "get") {
|
||||
return ky(path, {
|
||||
searchParams: {
|
||||
...paramArgs,
|
||||
...(args as typeof paramArgs),
|
||||
},
|
||||
method,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
timeout: 30000,
|
||||
retry: 0,
|
||||
});
|
||||
}
|
||||
return ky(path, {
|
||||
searchParams: {
|
||||
...paramArgs,
|
||||
},
|
||||
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,16 +0,0 @@
|
||||
import { APIPathGen } from "./APIPathGen.js";
|
||||
import type { APITail, RawArgType } from "../apiStructure/defs.js";
|
||||
import { structure } from "../apiStructure/sammoGatewayAPI.js";
|
||||
import { callClientAPI } from "./generator.js";
|
||||
|
||||
const apiRoot = process.env.API_ROOT_PATH ?? process.env.VITE_API_ROOT_PATH ?? '/rootAPI';
|
||||
|
||||
export const SammoGatewayAPI = APIPathGen(structure, (path: string[], tail: APITail, pathParam) => {
|
||||
const method = tail.reqType;
|
||||
return (args?: RawArgType, returnError?: boolean) => {
|
||||
if (returnError) {
|
||||
return callClientAPI(method, apiRoot, path.join('/'), args, pathParam, returnError);
|
||||
}
|
||||
return callClientAPI(method, apiRoot, path.join('/'), args, pathParam);
|
||||
};
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Mongoose, Schema, model } from "mongoose";
|
||||
|
||||
export interface IServerConfig {
|
||||
allowLogin: boolean;
|
||||
allowOAuthLogin: boolean;
|
||||
allowRegister: boolean;
|
||||
};
|
||||
|
||||
export const ServerConfig = new Schema<IServerConfig>({
|
||||
allowLogin: { type: Boolean, required: true },
|
||||
allowOAuthLogin: { type: Boolean, required: true },
|
||||
allowRegister: { type: Boolean, required: true },
|
||||
}, {
|
||||
autoIndex: false,
|
||||
autoCreate: false,
|
||||
capped: {
|
||||
max: 1,
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
//단일 document로 관리
|
||||
export default (conn:Mongoose)=>conn.model<IServerConfig>('ServerConfig', ServerConfig);
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Schema, model } from 'mongoose';
|
||||
|
||||
export type UserIDType = number;
|
||||
|
||||
const validOAuthTypeList = ['KAKAO', 'NONE'] as const;
|
||||
export type validOAuthType = typeof validOAuthTypeList[number];
|
||||
|
||||
const validServerActionTypeList = [
|
||||
'Update', 'UpdateByGitPath', 'ShowErrorLog',
|
||||
'CloseServer', 'OpenServer', 'StopAndResumeServer', 'OpenVote',
|
||||
] as const;
|
||||
export type ServerActionType = typeof validServerActionTypeList[number];
|
||||
|
||||
const validGatewayActionTypeList = [
|
||||
'Update', 'UpdateByGitPath', 'ShowErrorLog', 'ResetUserPassword', 'ChangeGatewayState', 'DeleteUser',
|
||||
] as const;
|
||||
|
||||
export type GatewayActionType = typeof validGatewayActionTypeList[number];
|
||||
|
||||
interface IUser {
|
||||
_id: UserIDType;
|
||||
|
||||
oauthID?: bigint;
|
||||
id: string;
|
||||
email: string;
|
||||
|
||||
oauthType: validOAuthType;
|
||||
oauthInfo?: object;
|
||||
tokenValidUntil?: Date;
|
||||
|
||||
userSalt: string;
|
||||
hashedPassword: string;
|
||||
|
||||
allowThirdPartyUse: boolean;
|
||||
|
||||
userName: string;
|
||||
allowServerAction?: Map<string, ServerActionType[]>;
|
||||
allowGatewayAction?: ServerActionType[];
|
||||
penalty?: Map<string, Date>;
|
||||
|
||||
picture?: string;
|
||||
useImgSvr?: boolean;
|
||||
|
||||
regDate: Date;
|
||||
deleteAfter?: Date;
|
||||
}
|
||||
|
||||
export const User = new Schema<IUser>({
|
||||
_id: { type: Number, required: true },
|
||||
|
||||
oauthID: { type: BigInt, required: false },
|
||||
id: { type: String, required: true },
|
||||
email: { type: String, required: true },
|
||||
|
||||
oauthType: { type: String, required: true, enum: validOAuthTypeList },
|
||||
oauthInfo: { type: Object, required: false },
|
||||
tokenValidUntil: { type: Date, required: false },
|
||||
|
||||
userSalt: { type: String, required: true },
|
||||
hashedPassword: { type: String, required: true },
|
||||
|
||||
allowThirdPartyUse: { type: Boolean, required: true },
|
||||
|
||||
userName: { type: String, required: true },
|
||||
allowServerAction: {
|
||||
type: Map, required: false, of: {
|
||||
type: Array,
|
||||
of: { type: String, enum: validServerActionTypeList }
|
||||
}
|
||||
},
|
||||
allowGatewayAction: {
|
||||
type: Array, required: false, of: {
|
||||
type: String, enum: validGatewayActionTypeList
|
||||
}
|
||||
},
|
||||
penalty: { type: Map, required: false, of: Date },
|
||||
|
||||
picture: { type: String, required: false },
|
||||
useImgSvr: { type: Boolean, required: false },
|
||||
|
||||
regDate: { type: Date, required: true },
|
||||
deleteAfter: { type: Date, required: false },
|
||||
}, { autoIndex: false, autoCreate: false })
|
||||
.index({ id: 1 }, { unique: true })
|
||||
.index({ email: 1 }, { unique: true })
|
||||
.index({ oauthID: 1 }, { unique: true, sparse: true })
|
||||
.index({ deleteAfter: 1 }) // 자동 삭제 아님!
|
||||
;
|
||||
|
||||
export default model<IUser>('User', User);
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Schema, model, } from 'mongoose';
|
||||
import { type UserIDType } from './User.js';
|
||||
|
||||
export type LogType = 'register'
|
||||
| 'login_pw' | 'login_token' | 'login_oauth' | 'logout'
|
||||
| 'change_pw' | 'reset_pw';
|
||||
const LogTypeList: LogType[] = [
|
||||
'register',
|
||||
'login_pw', 'login_token', 'login_oauth', 'logout',
|
||||
'change_pw', 'reset_pw',
|
||||
];
|
||||
|
||||
interface IUserLog {
|
||||
userID: UserIDType;
|
||||
logDate: Date;
|
||||
logType: LogType;
|
||||
action: object;
|
||||
}
|
||||
|
||||
export const UserLog = new Schema<IUserLog>({
|
||||
userID: { type: Number, required: true },
|
||||
logDate: { type: Date, required: true },
|
||||
logType: { type: String, required: true, enum: LogTypeList },
|
||||
action: { type: Object, required: true },
|
||||
}, { autoIndex: false, autoCreate: false, })
|
||||
.index({ userID: 1, logDate: 1 })
|
||||
.index({ logDate: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 365 * 3 })
|
||||
;
|
||||
|
||||
export default model<IUserLog>('UserLog', UserLog);
|
||||
@@ -1,3 +0,0 @@
|
||||
export type Entries<T> = {
|
||||
[K in keyof T]: [K, T[K]];
|
||||
}[keyof T][];
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
export class NotNullExpected extends TypeError {
|
||||
public override name = 'NotNullExpected';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
export class RuntimeError extends Error {
|
||||
public override name = 'RuntimeError';
|
||||
constructor(public override message: string = '') {
|
||||
super(message);
|
||||
}
|
||||
override toString(): string {
|
||||
if (this.message) {
|
||||
return this.name + ': ' + this.message;
|
||||
}
|
||||
else {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
export interface StateIncrementer {
|
||||
(state: number): Promise<number>;
|
||||
}
|
||||
|
||||
export type NumberGroup = {
|
||||
start: number;
|
||||
remain: number;
|
||||
}
|
||||
|
||||
const AllocatorGroupBucketSize = 100;
|
||||
const AllocatorPreserveThreshold = 50;
|
||||
|
||||
export function* numberGenerator(numberGroups: NumberGroup[]): Generator<number, null> {
|
||||
for (const numberGroup of numberGroups) {
|
||||
for (let i = 0; i < numberGroup.remain; i++) {
|
||||
yield numberGroup.start + i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export class UniqueNumberAllocator {
|
||||
private preservedNumbers: NumberGroup[];
|
||||
private incrementer: StateIncrementer;
|
||||
private totalRemain: number;
|
||||
private promisedCnt: number;
|
||||
private promise: Promise<void>;
|
||||
|
||||
public constructor(incrementer: StateIncrementer, initialState?: NumberGroup) {
|
||||
this.incrementer = incrementer;
|
||||
if (!initialState) {
|
||||
this.preservedNumbers = [];
|
||||
this.totalRemain = 0;
|
||||
}
|
||||
else {
|
||||
if (initialState.remain < 1) {
|
||||
throw new Error('initialState.remain must be greater than 0');
|
||||
}
|
||||
this.preservedNumbers = [initialState];
|
||||
this.totalRemain = initialState.remain;
|
||||
}
|
||||
this.promisedCnt = 0;
|
||||
this.promise = Promise.resolve();
|
||||
this.preserveNext();
|
||||
}
|
||||
|
||||
public static async generate(incrementer: StateIncrementer): Promise<UniqueNumberAllocator> {
|
||||
const initialStateEnd = await incrementer(AllocatorGroupBucketSize);
|
||||
const initialState: NumberGroup = {
|
||||
start: initialStateEnd - AllocatorGroupBucketSize + 1,
|
||||
remain: AllocatorGroupBucketSize,
|
||||
}
|
||||
return new UniqueNumberAllocator(incrementer, initialState);
|
||||
}
|
||||
|
||||
private preserveNext(): void {
|
||||
if (this.promisedCnt + this.totalRemain >= AllocatorGroupBucketSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.totalRemain >= AllocatorPreserveThreshold) {
|
||||
return;
|
||||
}
|
||||
|
||||
const waiter = this.promise;
|
||||
|
||||
this.promise = (async () => {
|
||||
this.promisedCnt += AllocatorGroupBucketSize;
|
||||
const nextP = this.incrementer(AllocatorGroupBucketSize);
|
||||
await waiter;
|
||||
const next = await nextP;
|
||||
if (this.preservedNumbers.length > 0) {
|
||||
const last = this.preservedNumbers[this.preservedNumbers.length - 1];
|
||||
if (last.start + last.remain === next - AllocatorGroupBucketSize + 1) {
|
||||
// 마지막 그룹과 연속됨
|
||||
last.remain += AllocatorGroupBucketSize;
|
||||
this.totalRemain += AllocatorGroupBucketSize;
|
||||
this.promisedCnt -= AllocatorGroupBucketSize;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const nextGroup: NumberGroup = {
|
||||
start: next - AllocatorGroupBucketSize + 1,
|
||||
remain: AllocatorGroupBucketSize,
|
||||
}
|
||||
this.preservedNumbers.push(nextGroup);
|
||||
this.totalRemain += AllocatorGroupBucketSize;
|
||||
this.promisedCnt -= AllocatorGroupBucketSize;
|
||||
})();
|
||||
}
|
||||
|
||||
public async allocateOne(): Promise<number> {
|
||||
if (this.totalRemain > 0) {
|
||||
const head = this.preservedNumbers[0];
|
||||
if (head.remain > 1) {
|
||||
const next = head.start;
|
||||
head.start++;
|
||||
head.remain--;
|
||||
this.totalRemain--;
|
||||
this.preserveNext();
|
||||
return next;
|
||||
}
|
||||
else {
|
||||
this.preservedNumbers.shift();
|
||||
this.totalRemain--;
|
||||
this.preserveNext();
|
||||
return head.start;
|
||||
}
|
||||
}
|
||||
|
||||
const next = (await this.allocate(1)).next().value;
|
||||
if (next === null) {
|
||||
throw new Error('incrementer failed to allocate enough numbers');
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
public async allocate(n: number): Promise<Generator<number, null>> {
|
||||
n = Math.ceil(n);
|
||||
if (n < 1) {
|
||||
throw new Error('n must be greater than 0');
|
||||
}
|
||||
|
||||
if (n > this.totalRemain + this.promisedCnt) {
|
||||
const endP = this.incrementer(n);
|
||||
this.preserveNext();
|
||||
const end = await endP;
|
||||
|
||||
return numberGenerator([{
|
||||
start: end - n + 1,
|
||||
remain: n,
|
||||
}]);
|
||||
}
|
||||
|
||||
const result: NumberGroup[] = [];
|
||||
|
||||
if (this.totalRemain > 0) {
|
||||
const head = this.preservedNumbers[0];
|
||||
const headRemain = head.remain;
|
||||
|
||||
if (n < headRemain) {
|
||||
result.push({
|
||||
start: head.start,
|
||||
remain: n,
|
||||
});
|
||||
head.start += n;
|
||||
head.remain -= n;
|
||||
this.totalRemain -= n;
|
||||
this.preserveNext();
|
||||
return numberGenerator(result);
|
||||
}
|
||||
if (n === headRemain) {
|
||||
this.preservedNumbers.shift();
|
||||
result.push(head);
|
||||
this.totalRemain -= n;
|
||||
this.preserveNext();
|
||||
return numberGenerator(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//assert n <= this.totalRemain + this.promisedRemain
|
||||
|
||||
if (n > this.totalRemain) {
|
||||
await this.promise;
|
||||
if (n > this.totalRemain) {
|
||||
throw new Error('incrementer failed to allocate enough numbers');
|
||||
}
|
||||
}
|
||||
|
||||
this.totalRemain -= n;
|
||||
this.preserveNext();
|
||||
|
||||
let remain = n;
|
||||
let idx = 0;
|
||||
while (remain > 0) {
|
||||
const head = this.preservedNumbers[idx];
|
||||
if (head.remain <= remain) {
|
||||
remain -= head.remain;
|
||||
result.push(head);
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push({
|
||||
start: head.start,
|
||||
remain: remain,
|
||||
});
|
||||
head.start += remain;
|
||||
head.remain -= remain;
|
||||
remain = 0;
|
||||
break;
|
||||
}
|
||||
this.preservedNumbers = this.preservedNumbers.slice(idx);
|
||||
|
||||
return numberGenerator(result);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { webcrypto } from "crypto";
|
||||
|
||||
const subtle = webcrypto.subtle;
|
||||
type BufferSource = ArrayBufferView | ArrayBuffer;
|
||||
|
||||
export async function AES_GCM_Encrypt(key: BufferSource, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer> {
|
||||
const keyObj = await subtle.importKey("raw", key, {
|
||||
name: "AES-GCM",
|
||||
}, false, ["encrypt"]);
|
||||
|
||||
const ciphertext = await subtle.encrypt({
|
||||
name: "AES-GCM",
|
||||
iv,
|
||||
additionalData: aad
|
||||
}, keyObj, msg);
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
|
||||
export async function AES_GCM_Decrypt(key: BufferSource, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer> {
|
||||
const keyObj = await subtle.importKey("raw", key, {
|
||||
name: "AES-GCM",
|
||||
}, false, ["decrypt"]);
|
||||
|
||||
const plaintext = await subtle.decrypt({
|
||||
name: "AES-GCM",
|
||||
iv: iv,
|
||||
additionalData: aad
|
||||
}, keyObj, ciphertext);
|
||||
//아마도 tag 검증 실패시 예외가 발생할 것으로 예상
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export function hexToRgb(hex: string): { r: number; g: number; b: number; } | null {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { unwrap } from ".//unwrap.js";
|
||||
import { hexToRgb } from "./hexToRgb.js";
|
||||
|
||||
export function isBrightColor(color: string): boolean {
|
||||
const cv = unwrap(hexToRgb(color));
|
||||
if ((cv.r * 0.299 + cv.g * 0.587 + cv.b * 0.114) > 140) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export function isNotNull<T>(value: T|null|undefined):value is T{
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import { every, isString } from "lodash-es";
|
||||
|
||||
type JsonifiableLite =
|
||||
string | number | boolean | bigint | null | undefined |
|
||||
Date | ArrayBuffer | SharedArrayBuffer | ArrayBufferView |
|
||||
JsonifiableLite[] | { [key: string]: JsonifiableLite };
|
||||
|
||||
export type Jsonifiable =
|
||||
JsonifiableLite |
|
||||
Jsonifiable[] |
|
||||
Map<JsonifiableLite, Jsonifiable> |
|
||||
Set<Jsonifiable> |
|
||||
{ jsonify(): Jsonifiable } |
|
||||
{ [key: string]: Jsonifiable };
|
||||
|
||||
export type Jsonified<T extends Jsonifiable> =
|
||||
T extends string ? string :
|
||||
T extends number ? number :
|
||||
T extends boolean ? boolean :
|
||||
T extends bigint ? string :
|
||||
T extends null ? null :
|
||||
T extends undefined ? undefined :
|
||||
T extends Date ? string :
|
||||
T extends ArrayBuffer ? string :
|
||||
T extends SharedArrayBuffer ? string :
|
||||
T extends ArrayBufferView ? string :
|
||||
T extends (infer K extends Jsonifiable)[] ? Jsonified<K>[] :
|
||||
T extends Map<infer K extends Jsonifiable, infer V extends Jsonifiable> ? (
|
||||
K extends string ? { [key in string]: Jsonified<V> } : [Jsonified<K>, Jsonified<V>][]) :
|
||||
T extends Set<infer V extends Jsonifiable> ? Jsonified<V>[] :
|
||||
T extends object ? (
|
||||
T extends { jsonify(): infer V extends Jsonifiable } ? Jsonified<V> : //HACK: jsonify()는 Jsonified여야 함
|
||||
{[key in keyof T]: Jsonified<T[key]>}):
|
||||
T extends unknown ? unknown :
|
||||
never;
|
||||
|
||||
|
||||
/**
|
||||
* Convert any object to JSON-safe object
|
||||
*/
|
||||
export function jsonify<T extends Jsonifiable>(item: T): Jsonified<T> {
|
||||
if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return item as any;
|
||||
}
|
||||
|
||||
if (typeof item === 'bigint') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return item.toString() as any;
|
||||
}
|
||||
|
||||
if (item === undefined || item === null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return item as any;
|
||||
}
|
||||
|
||||
if (item instanceof Date) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return item.toISOString() as any;
|
||||
}
|
||||
|
||||
if (item instanceof ArrayBuffer || item instanceof SharedArrayBuffer) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return Buffer.from(item).toString('base64') as any;
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(item)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return Buffer.from(item.buffer, item.byteOffset, item.byteLength).toString('base64') as any;
|
||||
}
|
||||
|
||||
if (Array.isArray(item)) {
|
||||
//HACK: depth hack
|
||||
const result: Jsonified<string>[] = [];
|
||||
for (const v of item) {
|
||||
result.push(jsonify(v as string) as Jsonified<string>);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return result as any;
|
||||
}
|
||||
|
||||
if (item instanceof Map) {
|
||||
//HACK: depth hack
|
||||
|
||||
const onlyStringKey = every(item.keys, isString);
|
||||
if (onlyStringKey) {
|
||||
const result: { [key: string]: Jsonified<string> } = {};
|
||||
for (const [k, v] of item.entries()) {
|
||||
result[k as string] = jsonify(v as string) as Jsonified<string>;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return result as any;
|
||||
}
|
||||
|
||||
const result: [Jsonified<string>, Jsonified<string>][] = [];
|
||||
for (const [k, v] of item.entries()) {
|
||||
result.push([jsonify(k as string), jsonify(v as string)] as [Jsonified<string>, Jsonified<string>]);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return result as any;
|
||||
}
|
||||
|
||||
if (item instanceof Set) {
|
||||
const result: Jsonified<string>[] = [];
|
||||
for (const v of item.values()) {
|
||||
result.push(jsonify(v as string) as Jsonified<string>);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return result as any;
|
||||
}
|
||||
|
||||
if (typeof item === 'object') {
|
||||
if (typeof (item as { jsonify(): unknown }).jsonify === 'function') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (item as { jsonify(): unknown }).jsonify() as any;
|
||||
}
|
||||
|
||||
const result: { [key: string]: Jsonified<string> } = {};
|
||||
for (const [k, v] of Object.entries(item)) {
|
||||
if (typeof k !== 'string') {
|
||||
continue;
|
||||
}
|
||||
if (typeof v === 'function') {
|
||||
continue;
|
||||
}
|
||||
result[k] = jsonify(v as string) as Jsonified<string>;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return result as any;
|
||||
}
|
||||
|
||||
throw new Error(`jsonify: invalid type ${typeof item}`);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { webcrypto } from "node:crypto";
|
||||
|
||||
const subtle = webcrypto.subtle;
|
||||
|
||||
export async function sha256(msg: ArrayBuffer): Promise<ArrayBuffer>{
|
||||
return await subtle.digest('SHA-256', msg);
|
||||
}
|
||||
|
||||
export async function sha512(msg: ArrayBuffer): Promise<ArrayBuffer>{
|
||||
return await subtle.digest('SHA-512', msg);
|
||||
}
|
||||
Reference in New Issue
Block a user