11/* eslint-disable @typescript-eslint/no-explicit-any */
2+ import { isPlainObject } from '../utils/validation' ;
3+
4+ /**
5+ * Custom type handler for Sanitizer.
6+ *
7+ * Allows user to register their own formatters from external packages.
8+ */
9+ export interface SanitizerTypeHandler {
10+ /**
11+ * Checks if this handler should be applied to given value
12+ *
13+ * @returns `true`
14+ */
15+ check : ( target : any ) => boolean ;
16+
17+ /**
18+ * Formats the value into a sanitized representation
19+ */
20+ format : ( target : any ) => any ;
21+ }
22+
223/**
324 * This class provides methods for preparing data to sending to Hawk
425 * - trim long strings
5- * - represent html elements like <div ...> as "<div>" instead of "{}"
626 * - represent big objects as "<big object>"
727 * - represent class as <class SomeClass> or <instance of SomeClass>
828 */
9- export default class Sanitizer {
29+ export class Sanitizer {
1030 /**
1131 * Maximum string length
1232 */
@@ -28,13 +48,31 @@ export default class Sanitizer {
2848 */
2949 private static readonly maxArrayLength : number = 10 ;
3050
51+ /**
52+ * Custom type handlers registered via {@link registerHandler}.
53+ *
54+ * Checked in {@link sanitize} before built-in type checks.
55+ */
56+ private static readonly customHandlers : SanitizerTypeHandler [ ] = [ ] ;
57+
3158 /**
3259 * Check if passed variable is an object
3360 *
3461 * @param target - variable to check
3562 */
3663 public static isObject ( target : any ) : boolean {
37- return Sanitizer . typeOf ( target ) === 'object' ;
64+ return isPlainObject ( target ) ;
65+ }
66+
67+ /**
68+ * Register a custom type handler.
69+ * Handlers are checked before built-in type checks, in reverse registration order
70+ * (last registered = highest priority).
71+ *
72+ * @param handler - handler to register
73+ */
74+ public static registerHandler ( handler : SanitizerTypeHandler ) : void {
75+ Sanitizer . customHandlers . unshift ( handler ) ;
3876 }
3977
4078 /**
@@ -60,19 +98,21 @@ export default class Sanitizer {
6098 */
6199 if ( Sanitizer . isArray ( data ) ) {
62100 return this . sanitizeArray ( data , depth + 1 , seen ) ;
101+ }
63102
64- /**
65- * If value is an Element, format it as string with outer HTML
66- * HTMLDivElement -> "<div ...></div>"
67- */
68- } else if ( Sanitizer . isElement ( data ) ) {
69- return Sanitizer . formatElement ( data ) ;
103+ // Check additional handlers provided by env-specific modules or users
104+ // to sanitize some additional cases (e.g. specific object types)
105+ for ( const handler of Sanitizer . customHandlers ) {
106+ if ( handler . check ( data ) ) {
107+ return handler . format ( data ) ;
108+ }
109+ }
70110
71- /**
72- * If values is a not-constructed class, it will be formatted as "<class SomeClass>"
73- * class Editor {...} -> <class Editor>
74- */
75- } else if ( Sanitizer . isClassPrototype ( data ) ) {
111+ /**
112+ * If values is a not-constructed class, it will be formatted as "<class SomeClass>"
113+ * class Editor {...} -> <class Editor>
114+ */
115+ if ( Sanitizer . isClassPrototype ( data ) ) {
76116 return Sanitizer . formatClassPrototype ( data ) ;
77117
78118 /**
@@ -131,7 +171,9 @@ export default class Sanitizer {
131171 * @param depth - current depth of recursion
132172 * @param seen - Set of already seen objects to prevent circular references
133173 */
134- private static sanitizeObject ( data : { [ key : string ] : any } , depth : number , seen : WeakSet < object > ) : Record < string , any > | '<deep object>' | '<big object>' {
174+ private static sanitizeObject ( data : {
175+ [ key : string ] : any
176+ } , depth : number , seen : WeakSet < object > ) : Record < string , any > | '<deep object>' | '<big object>' {
135177 /**
136178 * If the maximum depth is reached, return a placeholder
137179 */
@@ -205,24 +247,6 @@ export default class Sanitizer {
205247 return typeof target === 'string' ;
206248 }
207249
208- /**
209- * Return string representation of the object type
210- *
211- * @param object - object to get type
212- */
213- private static typeOf ( object : any ) : string {
214- return Object . prototype . toString . call ( object ) . match ( / \s ( [ a - z A - Z ] + ) / ) [ 1 ] . toLowerCase ( ) ;
215- }
216-
217- /**
218- * Check if passed variable is an HTML Element
219- *
220- * @param target - variable to check
221- */
222- private static isElement ( target : any ) : boolean {
223- return target instanceof Element ;
224- }
225-
226250 /**
227251 * Return name of a passed class
228252 *
@@ -248,31 +272,12 @@ export default class Sanitizer {
248272 */
249273 private static trimString ( target : string ) : string {
250274 if ( target . length > Sanitizer . maxStringLen ) {
251- return target . substr ( 0 , Sanitizer . maxStringLen ) + '…' ;
275+ return target . substring ( 0 , Sanitizer . maxStringLen ) + '…' ;
252276 }
253277
254278 return target ;
255279 }
256280
257- /**
258- * Represent HTML Element as string with it outer-html
259- * HTMLDivElement -> "<div ...></div>"
260- *
261- * @param target - variable to format
262- */
263- private static formatElement ( target : Element ) : string {
264- /**
265- * Also, remove inner HTML because it can be BIG
266- */
267- const innerHTML = target . innerHTML ;
268-
269- if ( innerHTML ) {
270- return target . outerHTML . replace ( target . innerHTML , '…' ) ;
271- }
272-
273- return target . outerHTML ;
274- }
275-
276281 /**
277282 * Represent not-constructed class as "<class SomeClass>"
278283 *
0 commit comments