From 209cee98b52e4d075c970634e4e3957820159bbe Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 Mar 2025 12:59:54 +0800 Subject: [PATCH] framework: disable prototype for kHandler --- build/prepare.js | 2 +- framework/framework/server.ts | 68 ++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/build/prepare.js b/build/prepare.js index 52cd75024..d2410fafa 100644 --- a/build/prepare.js +++ b/build/prepare.js @@ -15,7 +15,7 @@ const withoutTypes = (data) => ({ /** @type {import('typescript/lib/typescript').CompilerOptions} */ const compilerOptionsBase = { - target: 'es2020', + target: 'es2022', module: 'commonjs', esModuleInterop: true, moduleResolution: 'node', diff --git a/framework/framework/server.ts b/framework/framework/server.ts index 564395abc..70a9e19f1 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -19,6 +19,7 @@ import { CsrfTokenError, HydroError, InvalidOperationError, MethodNotAllowedError, NotFoundError, UserFacingError, } from './error'; +import type { KnownHandlers } from './interface'; import { Router } from './router'; import serializer from './serializer'; @@ -127,7 +128,7 @@ export interface UserModel { export interface HandlerCommon { } // eslint-disable-line @typescript-eslint/no-unused-vars export class HandlerCommon { - static [kHandler]: string | boolean = true; + static [kHandler]: string | boolean = 'HandlerCommon'; session: Record; args: Record; request: HydroRequest; @@ -198,6 +199,8 @@ export class HandlerCommon { } export class Handler extends HandlerCommon { + static [kHandler] = 'Handler'; + loginMethods: any; noCheckPermView = false; notUsage = false; @@ -244,6 +247,8 @@ export class Handler extends HandlerCommon { } export class ConnectionHandler extends HandlerCommon { + static [kHandler] = 'ConnectionHandler'; + conn: WebSocket; compression: Shorty; counter = 0; @@ -472,7 +477,8 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); const h = new HandlerClass(ctx, this.ctx); ctx.handler = h; const method = ctx.method.toLowerCase(); - const name = (typeof HandlerClass[kHandler] === 'string' ? HandlerClass[kHandler] : HandlerClass.name).replace(/Handler$/, ''); + const name = ((Object.hasOwn(HandlerClass, kHandler) && typeof HandlerClass[kHandler] === 'string') + ? HandlerClass[kHandler] : HandlerClass.name).replace(/Handler$/, ''); try { const operation = (method === 'post' && ctx.request.body?.operation) ? `_${ctx.request.body.operation}`.replace(/_([a-z])/gm, (s) => s[1].toUpperCase()) @@ -645,7 +651,8 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); private register(type: 'route' | 'conn', routeName: string, path: string, HandlerClass: any, ...permPrivChecker) { if (!HandlerClass?.[kHandler] || !isClass(HandlerClass)) throw new Error('Invalid registration.'); - const name = typeof HandlerClass[kHandler] === 'string' ? HandlerClass[kHandler] : HandlerClass.name; + const name = ((Object.hasOwn(HandlerClass, kHandler) && typeof HandlerClass[kHandler] === 'string') + ? HandlerClass[kHandler] : HandlerClass.name).replace(/Handler$/, ''); if (this.registrationCount[name] && this.registry[name] !== HandlerClass) { logger.warn('Route with name %s already exists.', name); } @@ -746,51 +753,46 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); this.captureAllRoutes[prefix] = cb; } - public handlerMixin(MixinClass: Partial>) { + private _applyMixin(Target: any, MixinClass: Partial) { + if (!('prototype' in Target)) throw new Error('Target must be a class.'); this.ctx.effect(() => { + let oldValue = null; for (const val of Object.getOwnPropertyNames(MixinClass)) { - if (HandlerCommon.prototype[val]) { - logger.warn('HandlerCommon.prototype[%s] already exists.', val); + if (Target.prototype[val]) { + logger.warn(`${Target.name}.prototype[${val}] already exists.`); + oldValue = Target.prototype[val]; } - HandlerCommon.prototype[val] = MixinClass[val]; + Target.prototype[val] = MixinClass[val]; } return () => { for (const val of Object.getOwnPropertyNames(MixinClass)) { - delete HandlerCommon.prototype[val]; + if (Target.prototype[val] !== MixinClass[val]) { + logger.warn(`Failed to unload mixin ${Target.name}.prototype[${val}]: not the same as the original value.`); + } else { + delete Target.prototype[val]; + if (oldValue) Target.prototype[val] = oldValue; + } } }; }); } + public applyMixin(name: T, MixinClass: any) { + this.withHandlerClass(name, (HandlerClass) => { + this._applyMixin(HandlerClass, MixinClass); + }); + } + + public handlerMixin(MixinClass: Partial>) { + return this._applyMixin(HandlerCommon, MixinClass); + } + public httpHandlerMixin(MixinClass: Partial>) { - this.ctx.effect(() => { - for (const val of Object.getOwnPropertyNames(MixinClass)) { - if (Handler.prototype[val]) { - logger.warn('Handler.prototype[%s] already exists.', val); - } - Handler.prototype[val] = MixinClass[val]; - } - return () => { - for (const val of Object.getOwnPropertyNames(MixinClass)) { - delete Handler.prototype[val]; - } - }; - }); + return this._applyMixin(Handler, MixinClass); } public wsHandlerMixin(MixinClass: Partial>) { - this.ctx.effect(() => { - for (const val of Object.getOwnPropertyNames(MixinClass)) { - if (ConnectionHandler.prototype[val]) { - logger.warn('ConnectionHandler.prototype[%s] already exists.', val); - } - } - return () => { - for (const val of Object.getOwnPropertyNames(MixinClass)) { - delete ConnectionHandler.prototype[val]; - } - }; - }); + return this._applyMixin(ConnectionHandler, MixinClass); } public registerRenderer(name: string, func: Renderer) {