changed network log format, player settings

This commit is contained in:
2025-07-26 00:17:14 -04:00
parent e604c7a437
commit 2302290d34
8 changed files with 126 additions and 27 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.env
/.env
/persist/
/worklist.txt

View File

@@ -10,6 +10,7 @@ import { verify } from "@hono/hono/jwt";
import { type ProfileToken } from "./server/profiles/types/profile.ts";
import { SignalRSocketHandler } from "./server/socket/signalr/socket.ts";
import { PushNotificationId } from "./server/socket/signalr/types.ts";
import { genericResponse } from "./util/api.ts";
LoggingConfiguration.resetTimeFormat = TimeFormat.Unix;
LoggingConfiguration.resetLogTiming = LogTiming.Microtask;
@@ -40,10 +41,7 @@ await routeImporter(AppRoot.app, 'src/', [
// deno-lint-ignore require-await
AppRoot.app.use('*', async c => {
if (!logBlacklist.includes(c.req.url)) log.e(detailedLog([c.get('srcAddr'),
`404 ${c.req.method} ${getFullPathFromUrl(new URL(c.req.url))}`
]));
c.res = new Response("Not Found", { status: 404 });
return c.json(genericResponse(false, "Resource Not Found"), 404);
});
export const consoleSockets: Set<SocketConsoleHandler> = new Set();
@@ -91,7 +89,7 @@ const server = Deno.serve({ hostname: "10.0.1.39", port: 13370, onListen: addr =
const payload = (await verify(splitHeader, secret)) as ProfileToken;
const profile = await Server.Profiles.get(payload.sub);
if (!profile) return new Response("Internal Server Error (profile)", { status: 500 });;
if (!profile) return new Response("Internal Server Error (profile)", { status: 500 });
const { response, socket } = Deno.upgradeWebSocket(req);
const handler = new SignalRSocketHandler(socket, profile);
gameSockets.add(handler);
@@ -120,14 +118,20 @@ const server = Deno.serve({ hostname: "10.0.1.39", port: 13370, onListen: addr =
}
if (!logBlacklist.includes(url.pathname)) log.n(detailedLog([srcAddr,
`${req.method} ${getFullPathFromUrl(new URL(req.url))}`,
const res = await AppRoot.app.fetch(req, { srcAddr });
const netlog = detailedLog([srcAddr,
`${res.status}: ${req.method} ${getFullPathFromUrl(new URL(req.url))}`,
formatHeader(req.headers, 'Content-Type'),
formatHeader(req.headers, 'Connection'),
formatHeader(req.headers, 'User-Agent'),
]));
]);
if (!logBlacklist.includes(url.pathname)) {
if (res.status === 404) log.e(netlog);
else log.n(netlog);
}
return await AppRoot.app.fetch(req, { srcAddr });
return res;
});

View File

@@ -3,6 +3,9 @@ import { authenticate } from "../../../util/api.ts";
import Server from "../../../server/server.ts";
import z from "zod";
import { typedZValidator } from "../../../util/validators.ts";
import Logging from "@proxnet/undead-logging";
const log = new Logging("AccountDebug");
export const route = createHonoRoute('/account');
@@ -29,10 +32,9 @@ route.app.get('/bulk', typedZValidator('query', bulkAccountQuerySchema), async c
);
});
route.app.use('*', authenticate);
route.app.use(authenticate);
route.app.get('/me', c => {
const profile = c.get('profile');
return c.json(profile.selfExport());
});

View File

@@ -0,0 +1,28 @@
import z from "zod";
import { authenticate, genericResponse } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts";
import { transformStringToEnum, typedZValidator } from "../../../util/validators.ts";
import { ProfileSetting } from "../../../server/profiles/content/Settings.ts";
import { trimTrailingSlash } from "@hono/hono/trailing-slash";
export const route = createHonoRoute('/settings');
route.app.use(authenticate, trimTrailingSlash());
route.app.get('/v2', async c => {
const profile = c.get('profile');
const settings = await profile.Settings.getAllSettings();
c.json(settings);
});
const settingsSetSchema = z.object({
Key: z.string().transform(transformStringToEnum<ProfileSetting>(ProfileSetting, true)),
Value: z.string()
});
route.app.post('/v2/set', typedZValidator('json', settingsSetSchema), async c => {
const { Key, Value } = c.req.valid('json');
if (!Key) return c.json(genericResponse(false, "Internal Server Error"), 500);
await c.get('profile').Settings.setSetting(Key, Value);
return c.status(200);
});

View File

@@ -0,0 +1,45 @@
import z from "zod";
import ProfileContentManager from "./base.ts";
export enum ProfileSetting {
Test = "test"
}
const genericSettingSchema = z.object({
Key: z.string(),
Value: z.string()
});
const settingsSchema = z.array(genericSettingSchema);
type Setting = z.infer<typeof genericSettingSchema>;
export class ProfileSettingsManager extends ProfileContentManager {
static settingsKey = "settings";
#key = this.profile.constructProfilePropertyKey(ProfileSettingsManager.settingsKey);
async getAllSettings(): Promise<Setting[]> {
const items = await this.kv.getKv().get(this.#key);
if (items.value == null) return [];
const parsed = settingsSchema.safeParse(items.value);
if (parsed.success) return parsed.data;
else return [];
}
async getSetting(setting: ProfileSetting) {
const settings = await this.getAllSettings();
return settings.find(s => s.Key == setting)?.Value || null;
}
async #updateSettings(settings: Setting[]) {
await this.kv.getKv().set(this.#key, settings);
}
async setSetting(Key: ProfileSetting, Value: string) {
const settings = await this.getAllSettings();
const s = settings.find(setting => setting.Key === Key);
if (!s) settings.push({ Key, Value });
else s.Value = Value;
await this.#updateSettings(settings);
}
}

View File

@@ -1,5 +1,15 @@
import type KV from "../../persistence/kv.ts";
import type Profile from "../profile.ts";
class ProfileContentManager {
protected profile: Profile;
protected kv: KV;
constructor(profile: Profile, kv: KV) {
this.profile = profile;
this.kv = kv;
}
}
export default ProfileContentManager;

View File

@@ -2,6 +2,7 @@ import KV from "../persistence/kv.ts";
import { ProfileRole } from "../platforms/base.ts";
import { type ServerBase } from "../server.ts";
import { type SignalRSocketHandler } from "../socket/signalr/socket.ts";
import { ProfileSettingsManager } from "./content/Settings.ts";
import ProfileManagerBase from "./manager.ts";
import { recNetAccountSchema, SelfAccount, type RecNetAccount } from "./types/profile.ts";
@@ -15,19 +16,23 @@ class Profile {
#selfAcc: SelfAccount;
Settings: ProfileSettingsManager;
constructor(acc: SelfAccount, kv: KV, server: ServerBase) {
this.#id = acc.accountId;
this.#selfAcc = acc;
this.#kv = kv;
this.#server = server;
this.Settings = new ProfileSettingsManager(this, this.#kv);
}
async #saveSelfAcc() {
await this.#kv.getKv().set(this.#constructProfilePropertyKey(), this.#selfAcc);
await this.#kv.getKv().set(this.constructProfilePropertyKey(), this.#selfAcc);
this.#server.emit('profile.update', { profile: this });
}
#constructProfilePropertyKey(...keys: (string | undefined)[]) {
constructProfilePropertyKey(...keys: (string | undefined)[]) {
return [ ProfileManagerBase.profilesKey, this.#id, ...keys.filter(val => typeof val == 'string') ];
}
@@ -51,24 +56,24 @@ class Profile {
}
async getBio(){
const key = this.#constructProfilePropertyKey('bio');
const key = this.constructProfilePropertyKey('bio');
const val = await this.#kv.getKv().get<string>(key);
if (!val.value) return null;
else return val.value;
}
async setBio(bio: string) {
const key = this.#constructProfilePropertyKey('bio');
const key = this.constructProfilePropertyKey('bio');
await this.#kv.getKv().set(key, bio);
}
async getRole(): Promise<ProfileRole> {
const val = (await this.#kv.getKv().get<ProfileRole>(this.#constructProfilePropertyKey('role'))).value;
const val = (await this.#kv.getKv().get<ProfileRole>(this.constructProfilePropertyKey('role'))).value;
if (!val) return ProfileRole.User;
else return val;
}
async setRole(role: ProfileRole) {
await this.#kv.getKv().set(this.#constructProfilePropertyKey('role'), role);
await this.#kv.getKv().set(this.constructProfilePropertyKey('role'), role);
this.#server.emit('profile.roleupdate', { profile: this, newRole: role });
}

View File

@@ -18,13 +18,17 @@ export const typedZValidator = <T extends z.ZodSchema>(
>;
};
export const transformStringToEnum = <T>(anEnum: { [s: number]: string }) => {
return (arg: string, ctx: z.RefinementCtx<string>) => {
const int = parseInt(arg);
if (isNaN(int)) ctx.addIssue("Must be parseable as a number");
else {
if (anEnum[int]) return int as T;
else ctx.addIssue("Number must be a valid enum member");
export const transformStringToEnum = <T>(anEnum: { [s: string]: string | number }, str?: boolean) => {
return (arg: string, ctx: z.RefinementCtx<string | number>) => {
if (!str) {
const int = parseInt(arg);
if (isNaN(int) || !Number.isSafeInteger(int)) ctx.addIssue("number was not valid");
else if (anEnum[int]) return int as T;
else ctx.addIssue("number was not a valid enum member");
} else {
const vals = Object.values(anEnum);
if (vals.includes(arg)) return arg as T;
else ctx.addIssue("string was not a valid enum member");
}
}
}