init type updates

This commit is contained in:
MathMan05
2026-06-20 13:59:17 -05:00
parent 98c07855da
commit 2fdc089f68
23 changed files with 442 additions and 2 deletions
+1
View File
@@ -147,6 +147,7 @@
</body>
<script src="/index.js" type="module"></script>
<!-- <script src="/service.js" --->
<!-- <script src="/typeChecker/chekerIndex.js" --->
<!-- <script src="/audio/worklet/worklet.js" --->
<!-- <script src="/utils/dirrWorker.js" --->
</html>
+12
View File
@@ -18,6 +18,7 @@ import "./oauth2/auth.js";
import "./audio/page.js";
import "./404.js";
import {Channel} from "./channel.js";
import type * as C from "./typeChecker/chekerIndex.js";
if (window.location.pathname === "/app") {
window.location.pathname = "/channels/@me";
@@ -25,6 +26,17 @@ if (window.location.pathname === "/app") {
export interface CustomHTMLDivElement extends HTMLDivElement {
markdown: MarkDown;
}
declare global {
interface Window {
checker?: typeof C.Check;
}
}
if (localStorage.getItem("checkTypes")) {
const i = (await import(
"/typeChecker/chekerIndex.js" as "./typeChecker/chekerIndex.js"
)) as typeof C;
window.checker = i.Check;
}
if (window.location.pathname.startsWith("/channels")) {
let templateID = new URLSearchParams(window.location.search).get("templateID");
await I18n.done;
+5
View File
@@ -722,6 +722,11 @@ class Localuser {
}
conectionChange = () => {};
async handleEvent(temp: wsjson) {
try {
window.checker?.checkEvent(temp);
} catch (e) {
console.error(e);
}
if (temp.d._trace) this.handleTrace(temp.d._trace);
if (getDeveloperSettings().gatewayLogging) console.debug(temp);
if (temp.s) this.lastSequence = temp.s;
+41
View File
@@ -0,0 +1,41 @@
import {ArrayChecker} from "./checkers/array";
import {BoolChecker} from "./checkers/bool";
import {Never} from "./checkers/never";
import {NumberChecker} from "./checkers/number";
import {ObjectChecker} from "./checkers/object";
import {StringChecker} from "./checkers/string";
export abstract class TypeChecker {
abstract check(obj: unknown): void;
static resolve(r: resolveable): TypeChecker {
if (r instanceof TypeChecker) return r;
if (typeof r === "string" || r instanceof RegExp) {
return new StringChecker(r);
} else if (r === String) {
return new StringChecker();
} else if (r === Boolean) {
return new BoolChecker();
} else if (r === Number) {
return new NumberChecker();
} else if (typeof r === "number") {
return new NumberChecker(r);
} else if (r instanceof Array) {
return new ArrayChecker(r);
} else if (r instanceof Object) {
return new ObjectChecker(r as {[key: string]: resolveable});
} else {
return new Never();
}
}
}
export type resolveable =
| typeof String
| string
| number
| typeof Number
| RegExp
| resolveable[]
| {[key: string]: resolveable}
| TypeChecker
| typeof Boolean;
+10
View File
@@ -0,0 +1,10 @@
import {TypeChecker} from "../basetype";
export class Any extends TypeChecker {
constructor() {
super();
}
check() {
return;
}
}
+26
View File
@@ -0,0 +1,26 @@
import {resolveable, TypeChecker} from "../basetype";
export class ArrayChecker extends TypeChecker {
arr: TypeChecker | TypeChecker[];
constructor(arr: resolveable | resolveable[]) {
super();
if (arr instanceof Array) {
this.arr = arr.map((_) => TypeChecker.resolve(_));
} else {
this.arr = TypeChecker.resolve(arr);
}
}
check(obj: unknown) {
if (!(obj instanceof Array)) throw new Error(`${obj} is not of type array`);
if (this.arr instanceof Array) {
if (this.arr.length !== obj.length) throw new Error(`${obj} does not match array schema`);
for (let i = 0; i < obj.length; i++) {
this.arr[i].check(obj[i]);
}
} else {
for (const elm of obj) {
this.arr.check(elm);
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
import {TypeChecker} from "../basetype";
export class BoolChecker extends TypeChecker {
constructor() {
super();
}
check(obj: unknown): void {
if (typeof obj !== "boolean") throw new Error(`${obj} is not a bool`);
}
}
+10
View File
@@ -0,0 +1,10 @@
import {TypeChecker} from "../basetype";
export class Never extends TypeChecker {
constructor() {
super();
}
check(): void {
throw new Error(`never`);
}
}
+10
View File
@@ -0,0 +1,10 @@
import {TypeChecker} from "../basetype";
export class NullChecker extends TypeChecker {
constructor() {
super();
}
check(obj: unknown): void {
if (obj !== null) throw new Error(`${obj} is not null`);
}
}
@@ -0,0 +1,13 @@
import {resolveable, TypeChecker} from "../basetype";
export class Nullish extends TypeChecker {
checker: TypeChecker;
constructor(check: resolveable) {
super();
this.checker = TypeChecker.resolve(check);
}
check(obj: unknown) {
if (obj === null) return;
this.checker.check(obj);
}
}
@@ -0,0 +1,14 @@
import {TypeChecker} from "../basetype";
export class NumberChecker extends TypeChecker {
num?: number;
constructor(num?: number) {
super();
this.num = num;
}
check(obj: unknown): void {
if (this.num === undefined) {
if (typeof obj !== "number") throw new Error(`${obj} is not a number`);
} else if (this.num !== obj) throw new Error(`${obj} and ${this.num} do not match`);
}
}
@@ -0,0 +1,38 @@
import {resolveable, TypeChecker} from "../basetype";
export class ObjectChecker extends TypeChecker {
obj: {[key: string]: TypeChecker} | [TypeChecker, TypeChecker] | [TypeChecker];
constructor(obj: {[key: string]: resolveable} | [resolveable] | [resolveable, resolveable]) {
super();
if (obj instanceof Array) {
this.obj = obj.map((_) => TypeChecker.resolve(_)) as
| [TypeChecker, TypeChecker]
| [TypeChecker];
} else {
this.obj = Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, TypeChecker.resolve(value)]),
);
}
}
check(obj: unknown) {
if (!(obj instanceof Object)) throw new Error(`${obj} is not of type object`);
if (this.obj instanceof Array) {
const valueCheck = this.obj[this.obj.length - 1];
const keyCheck = this.obj.at(-2);
for (const [key, value] of Object.entries(obj)) {
keyCheck?.check(key);
valueCheck.check(value);
}
} else {
const thiskeys = Object.keys(this.obj);
for (const key of thiskeys) {
this.obj[key].check((obj as Record<string, unknown>)[key]);
}
const objKeys = Object.keys(obj);
const extra = new Set(objKeys).difference(new Set(thiskeys));
if (extra.size) {
throw new Error(`Object has extra keys ${[...extra]}`);
}
}
}
}
@@ -0,0 +1,13 @@
import {resolveable, TypeChecker} from "../basetype";
export class Optional extends TypeChecker {
checker: TypeChecker;
constructor(check: resolveable) {
super();
this.checker = TypeChecker.resolve(check);
}
check(obj: unknown) {
if (obj === undefined) return;
this.checker.check(obj);
}
}
+21
View File
@@ -0,0 +1,21 @@
import {resolveable, TypeChecker} from "../basetype";
export class OrChecker extends TypeChecker {
arr: TypeChecker[];
constructor(arr: resolveable[]) {
super();
this.arr = arr.map((_) => TypeChecker.resolve(_));
}
check(obj: unknown) {
let err = new Error("or statement is missing things to or");
for (const elm of this.arr) {
try {
elm.check(obj);
return;
} catch (e) {
err = e as Error;
}
}
throw err;
}
}
@@ -0,0 +1,17 @@
import {TypeChecker} from "../basetype";
export class StringChecker extends TypeChecker {
str?: string | RegExp;
constructor(str?: string | RegExp) {
super();
this.str = str;
}
check(obj: unknown): void {
if (this.str === undefined) {
if (typeof obj !== "string") throw new Error(`${obj} is not a string`);
} else if (this.str instanceof RegExp) {
if (typeof obj !== "string") throw new Error(`${obj} is not a string`);
if (!obj.match(this.str)) throw new Error(`${obj} does not match ${this.str}`);
} else if (this.str !== obj) throw new Error(`${obj} does not match ${this.str}`);
}
}
+45
View File
@@ -0,0 +1,45 @@
import {TypeChecker} from "./basetype";
import {basicEvent} from "./types/basicEvent";
import {heartbeat} from "./types/events/hearbeat";
import {ready} from "./types/events/ready";
import {initHeart} from "./types/events/setInterval";
type basicgw = {
op: number;
d?: unknown;
s?: number;
t?: string;
};
export class Check {
static apiMatch = [] as [RegExp, TypeChecker][];
static checkAPI(path: string, obj: unknown) {
for (const [match, check] of this.apiMatch) {
if (path.match(match)) {
check.check(obj);
return;
}
}
console.warn(`could not check API path: ${path}`);
}
static checkDispatch(dis: basicgw) {
switch (dis.t) {
case "READY":
return ready.check(dis);
default:
console.warn(`could not check gateway dispatch: ${dis.t}`);
}
}
static checkEvent(obj: unknown) {
basicEvent.check(obj);
const o = obj as basicgw;
switch (o.op) {
case 0:
return this.checkDispatch(o);
case 10:
return initHeart.check(o);
case 11:
return heartbeat.check(o);
default:
console.warn(`could not check gateway: ${o.op}`);
}
}
}
@@ -0,0 +1,11 @@
import {Any} from "../checkers/any";
import {Nullish} from "../checkers/nullish";
import {ObjectChecker} from "../checkers/object";
import {Optional} from "../checkers/optional";
export const basicEvent = new ObjectChecker({
op: Number,
d: new Nullish(new Optional(new Any())),
s: new Nullish(new Optional(Number)),
t: new Nullish(new Optional(String)),
});
@@ -0,0 +1,10 @@
import {Nullish} from "../../checkers/nullish";
import {ObjectChecker} from "../../checkers/object";
import {Optional} from "../../checkers/optional";
export const heartbeat = new ObjectChecker({
op: Number,
d: {},
s: new Nullish(new Optional(Number)),
t: new Nullish(new Optional(String)),
});
@@ -0,0 +1,51 @@
import {ArrayChecker} from "../../checkers/array";
import {Nullish} from "../../checkers/nullish";
import {ObjectChecker} from "../../checkers/object";
import {Optional} from "../../checkers/optional";
import {fulluser} from "../objects/user";
export const ready = new ObjectChecker({
op: Number,
d: {
_trace: new Optional(new ArrayChecker(String)),
v: Number,
user: fulluser,
//TODO user_settings
user_settings_proto: String,
notification_settings: {
flags: Number,
},
user_guild_settings: {
entries: new ArrayChecker({
channel_overrides: new ArrayChecker({
message_notifications: Number,
muted: Boolean,
mute_config: new Nullish({
selected_time_window: Number,
end_time: Number,
}),
channel_id: String,
}),
message_notifications: Number,
flags: Number,
hide_muted_channels: Boolean,
mobile_push: Boolean,
mute_config: new Nullish({
selected_time_window: Number,
end_time: Number,
}),
mute_scheduled_events: Boolean,
muted: Boolean,
notify_highlights: Number,
suppress_everyone: Boolean,
suppress_roles: Boolean,
version: Number,
guild_id: String,
}),
partial: Boolean,
version: Number,
},
},
s: new Nullish(new Optional(Number)),
t: new Nullish(new Optional(String)),
});
@@ -0,0 +1,12 @@
import {Nullish} from "../../checkers/nullish";
import {ObjectChecker} from "../../checkers/object";
import {Optional} from "../../checkers/optional";
export const initHeart = new ObjectChecker({
op: Number,
d: {
heartbeat_interval: Number,
},
s: new Nullish(new Optional(Number)),
t: new Nullish(new Optional(String)),
});
@@ -0,0 +1,67 @@
import {Nullish} from "../../checkers/nullish";
import {ObjectChecker} from "../../checkers/object";
import {Optional} from "../../checkers/optional";
import {snowflake} from "../snowflake";
export const fulluser = new ObjectChecker({
id: snowflake,
username: String,
discriminator: /\d\d\d\d/,
global_name: new Nullish(new Optional(String)),
avatar: new Nullish(String),
avatar_decoration_data: new Nullish(
new Optional({
asset: String,
sku_id: String,
}),
),
//TODO collectibles, display_name_styles, primary_guild, linked_users, premium_state
bot: new Optional(Boolean),
system: new Optional(Boolean),
mfa_enabled: Boolean,
nsfw_allowed: new Nullish(new Optional(Boolean)),
age_verification_status: new Optional(Number),
pronouns: new Optional(String),
bio: String,
banner: new Optional(new Nullish(String)),
accent_color: new Optional(new Nullish(Number)),
locale: new Optional(String),
verified: new Optional(Boolean),
email: new Nullish(String),
phone: new Optional(new Nullish(String)),
premium: Boolean,
premium_type: Number,
personal_connection_id: new Optional(snowflake),
flags: new Optional(Number),
public_flags: Number,
purchased_flags: new Optional(Number),
premium_usage_flags: new Optional(Number),
desktop: new Optional(Number),
mobile: new Optional(Number),
has_bounced_email: new Optional(Boolean),
authenticator_types: new Optional(new Array(Number)),
analytics_token: new Optional(String),
});
export const partialuser = new ObjectChecker({
id: snowflake,
username: String,
discriminator: /\d\d\d\d/,
global_name: new Nullish(new Optional(String)),
avatar: new Nullish(String),
avatar_decoration_data: new Nullish(
new Optional({
asset: String,
sku_id: String,
}),
),
//TODO collectibles, display_name_styles, primary_guild, linked_users
bot: new Optional(Boolean),
system: new Optional(Boolean),
mfa_enabled: Boolean,
nsfw_allowed: new Nullish(new Optional(Boolean)),
age_verification_status: new Optional(Number),
pronouns: new Optional(String),
bio: String,
banner: new Optional(new Nullish(String)),
accent_color: new Optional(new Nullish(Number)),
public_flags: new Optional(Number),
});
@@ -0,0 +1,3 @@
import {StringChecker} from "../checkers/string";
export const snowflake = new StringChecker(/\d*/);
+2 -2
View File
@@ -224,7 +224,7 @@
"gatewayComp": "Disable Gateway compression:",
"reportSystem":"Enable experimental reporting system:",
"logGateway": "Log received gateway events (log level info):",
"name": "Developer Settings",
"name": "Developer settings",
"traces": "Expose traces:"
},
"deviceManage": {
@@ -554,7 +554,7 @@
"themesAndSounds": "Themes & Sounds",
"tokenDisplay": "Token: $1",
"trace": "Traces",
"trusted": "Trusted Domains",
"trusted": "Trusted domains",
"trustedDesc": "When you click on links sending you to these domain, you will ***not*** be prompted for permission to open like other links, only give this to domains you trust, such as 'https://fermi.chat'",
"updateSettings": "Update settings",
"updatesYay": "Updates found!",