diff --git a/README.md b/README.md index e6f2c57..9cd5091 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,10 @@ Mozilla's Readability library is used under the hood. - Image compression with Sharp - Rendering client-side apps (Vanilla, React, Vue, etc) with [webder](https://github.com/TxtDot/webder) - Search with SearXNG -- Custom parsers for StackOverflow and SearXNG - Handy API endpoints - No client JavaScript - Some kind of Material Design 3 -- Customization with plugins, see [@txtdot/sdk](https://github.com/TxtDot/sdk) and [@txtdot/plugins](https://github.com/TxtDot/plugins) +- Customization with plugins, see [@txtdot/sdk](https://github.com/TxtDot/txtdot/tree/main/packages/sdk) and [@txtdot/plugins](https://github.com/TxtDot/txtdot/tree/main/packages/plugins) ## Running diff --git a/packages/plugins/src/engines/readability.ts b/packages/plugins/src/engines/readability.ts index f35d8f3..fe29c9b 100644 --- a/packages/plugins/src/engines/readability.ts +++ b/packages/plugins/src/engines/readability.ts @@ -1,7 +1,5 @@ import { Readability as OReadability } from '@mozilla/readability'; - -import { Engine, EngineParseError } from '@txtdot/sdk'; -import { parseHTML } from 'linkedom'; +import { Engine, EngineParseError, Route } from '@txtdot/sdk'; const Readability = new Engine( 'Readability', @@ -9,8 +7,8 @@ const Readability = new Engine( ['*'] ); -Readability.route('*path', async (input, ro) => { - const reader = new OReadability(input.document); +Readability.route('*path', async (input, ro: Route<{ path: string }>) => { + const reader = new OReadability(input.document.cloneNode(true) as Document); const parsed = reader.parse(); if (!parsed) { diff --git a/packages/plugins/src/engines/searx.tsx b/packages/plugins/src/engines/searx.tsx index cb7c2d3..2bffe93 100644 --- a/packages/plugins/src/engines/searx.tsx +++ b/packages/plugins/src/engines/searx.tsx @@ -1,6 +1,5 @@ import { Engine, JSX } from '@txtdot/sdk'; import { HandlerInput, Route } from '@txtdot/sdk'; -import { parseHTML } from 'linkedom'; import { PageFooter, ResultItem } from '../components/searchers'; const SearX = new Engine('SearX', "Engine for searching with 'SearXNG'", [ diff --git a/packages/plugins/src/lib.ts b/packages/plugins/src/lib.ts index a06217c..7ab3600 100644 --- a/packages/plugins/src/lib.ts +++ b/packages/plugins/src/lib.ts @@ -1,15 +1,16 @@ import * as engines from './engines'; - export { engines }; - export const engineList = [ engines.StackOverflow, engines.SearX, engines.Readability, ]; -import { compile } from 'html-to-text'; +import * as middlewares from './middlewares'; +export { middlewares }; +export const middlewareList = [middlewares.Highlight, middlewares.HabrNav]; +import { compile } from 'html-to-text'; export const html2text = compile({ longWordSplit: { forceWrapOnLimit: true, diff --git a/packages/plugins/src/middlewares/highlight.tsx b/packages/plugins/src/middlewares/highlight.tsx new file mode 100644 index 0000000..66094d6 --- /dev/null +++ b/packages/plugins/src/middlewares/highlight.tsx @@ -0,0 +1,39 @@ +import { Middleware, JSX } from '@txtdot/sdk'; + +const Highlight = new Middleware( + 'Highlight', + 'Highlights code with highlight.js only when needed', + ['*'] +); + +Highlight.use(async (input, ro, out) => { + if (out.content.indexOf(', + }; + + return out; +}); + +function Highlighter({ content }: { content: string }) { + return ( + <> + + + {content} + + ); +} + +export default Highlight; diff --git a/packages/plugins/src/middlewares/index.ts b/packages/plugins/src/middlewares/index.ts new file mode 100644 index 0000000..181771d --- /dev/null +++ b/packages/plugins/src/middlewares/index.ts @@ -0,0 +1,4 @@ +import Highlight from './highlight'; +import { HabrNav } from './navigation'; + +export { Highlight, HabrNav }; diff --git a/packages/plugins/src/middlewares/navigation.tsx b/packages/plugins/src/middlewares/navigation.tsx new file mode 100644 index 0000000..ed22096 --- /dev/null +++ b/packages/plugins/src/middlewares/navigation.tsx @@ -0,0 +1,27 @@ +import { Middleware, JSX } from '@txtdot/sdk'; + +const HabrNav = new Middleware('Habr Nav', 'Adds navigation in habr pages', [ + 'habr.com', +]); + +HabrNav.use(async (input, ro, out) => { + let nav = [...input.document.querySelectorAll('.tm-main-menu__item')]; + + return { + ...out, + content: ( + <> + + {out.content} + + ), + }; +}); + +export { HabrNav }; diff --git a/packages/sdk/src/engine.ts b/packages/sdk/src/engine.ts index 601e647..e085945 100644 --- a/packages/sdk/src/engine.ts +++ b/packages/sdk/src/engine.ts @@ -34,7 +34,7 @@ export class Engine { } async handle(input: HandlerInput): Promise { - const url = new URL(input.getUrl()); + const url = new URL(input.url); const path = url.pathname + url.search + url.hash; for (const route of this.routes) { const match = route.route.match(path); diff --git a/packages/sdk/src/jsx.ts b/packages/sdk/src/jsx.ts index 81f921f..d5850ba 100644 --- a/packages/sdk/src/jsx.ts +++ b/packages/sdk/src/jsx.ts @@ -24,9 +24,7 @@ export function createElement( }) .join(' '); - return inner.length === 0 - ? `<${name} ${propsstr}/>` - : `<${name} ${propsstr}>${content}`; + return `<${name} ${propsstr}>${content}`; } else if (typeof name === 'function') { return name(props, content); } else { diff --git a/packages/sdk/src/lib.ts b/packages/sdk/src/lib.ts index 31909b6..3250c63 100644 --- a/packages/sdk/src/lib.ts +++ b/packages/sdk/src/lib.ts @@ -1,4 +1,5 @@ import { Engine } from './engine'; +import { Middleware } from './middleware'; import { EngineParseError, @@ -16,17 +17,22 @@ import { HandlerOutput, Route, handlerSchema, + EngineOutput, + MiddleFunction, } from './types/handler'; import * as JSX from './jsx'; export { Engine, + Middleware, EngineParseError, NoHandlerFoundError, TxtDotError, EngineFunction, + MiddleFunction, EngineMatch, + EngineOutput, Engines, RouteValues, EnginesMatch, diff --git a/packages/sdk/src/middleware.ts b/packages/sdk/src/middleware.ts new file mode 100644 index 0000000..6b5c627 --- /dev/null +++ b/packages/sdk/src/middleware.ts @@ -0,0 +1,61 @@ +import Route from 'route-parser'; + +import { + HandlerInput, + RouteValues, + EngineOutput, + MiddleFunction, +} from './types/handler'; + +interface IMiddle { + route: Route; + handler: MiddleFunction; +} + +export class Middleware { + name: string; + description: string; + domains: string[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + middles: IMiddle[] = []; + constructor(name: string, description: string, domains: string[] = []) { + this.domains = domains; + this.name = name; + this.description = description; + } + + route( + path: string, + handler: MiddleFunction + ) { + this.middles.push({ route: new Route(path), handler }); + } + + use(handler: MiddleFunction) { + this.middles.push({ route: new Route<{ path: string }>('*path'), handler }); + } + + async handle(input: HandlerInput, out: EngineOutput): Promise { + const url = new URL(input.url); + const path = url.pathname + url.search + url.hash; + + let processed_out = out; + + for (const middle of this.middles) { + const match = middle.route.match(path); + + if (match) { + processed_out = await middle.handler( + input, + { + q: match, + reverse: (req) => middle.route.reverse(req), + }, + out + ); + } + } + + return processed_out; + } +} diff --git a/packages/sdk/src/types/handler.ts b/packages/sdk/src/types/handler.ts index 30b2eb0..6e4fcd7 100644 --- a/packages/sdk/src/types/handler.ts +++ b/packages/sdk/src/types/handler.ts @@ -2,26 +2,30 @@ import { parseHTML } from 'linkedom'; import { Engine } from '../engine'; export class HandlerInput { - private data: string; - private url: string; - private window?: Window; + private _data: string; + private _url: string; + private _window?: Window; constructor(data: string, url: string) { - this.data = data; - this.url = url; + this._data = data; + this._url = url; } - getUrl(): string { - return this.url; + get url(): string { + return this._url; + } + + get data(): string { + return this._data; } get document(): Document { - if (this.window) { - return this.window.document; + if (this._window) { + return this._window.document; } - this.window = parseHTML(this.data); - return this.window.document; + this._window = parseHTML(this._data); + return this._window.document; } } @@ -75,6 +79,12 @@ export type EngineFunction = ( ro: Route ) => Promise; +export type MiddleFunction = ( + input: HandlerInput, + ro: Route, + out: EngineOutput +) => Promise; + export type EnginesMatch = EngineMatch[]; export interface Route { diff --git a/packages/server/package.json b/packages/server/package.json index 077012f..ac4ac7b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -26,19 +26,18 @@ "@txtdot/plugins": "workspace:*", "@txtdot/sdk": "workspace:*", "axios": "^1.6.8", - "dompurify": "^3.1.2", "dotenv": "^16.3.1", "ejs": "^3.1.10", "fastify": "^4.26.2", "iconv-lite": "^0.6.3", "ip-range-check": "^0.2.0", + "isomorphic-dompurify": "^2.10.0", "json-schema-to-ts": "^3.0.1", "linkedom": "^0.18.0", "micromatch": "^4.0.5", "sharp": "^0.33.3" }, "devDependencies": { - "@types/dompurify": "^3.0.5", "@types/ejs": "^3.1.5", "@types/jsdom": "^21.1.6", "@types/micromatch": "^4.0.7", diff --git a/packages/server/src/config/pluginConfig.ts b/packages/server/src/config/pluginConfig.ts index 3fc3dcb..c426f6c 100644 --- a/packages/server/src/config/pluginConfig.ts +++ b/packages/server/src/config/pluginConfig.ts @@ -1,5 +1,5 @@ import { IAppConfig } from '../types/pluginConfig'; -import { engineList, html2text } from '@txtdot/plugins'; +import { engineList, middlewareList, html2text } from '@txtdot/plugins'; /** * Configuration of plugins @@ -7,6 +7,7 @@ import { engineList, html2text } from '@txtdot/plugins'; */ const plugin_config: IAppConfig = { engines: [...engineList], + middlewares: [...middlewareList], html2text, }; diff --git a/packages/server/src/distributor.ts b/packages/server/src/distributor.ts index bb1a037..9a24cfa 100644 --- a/packages/server/src/distributor.ts +++ b/packages/server/src/distributor.ts @@ -1,16 +1,16 @@ import axios, { oaxios } from './types/axios'; import micromatch from 'micromatch'; -import DOMPurify from 'dompurify'; import { Readable } from 'stream'; import { NotHtmlMimetypeError } from './errors/main'; import { decodeStream, parseEncodingName } from './utils/http'; import replaceHref from './utils/replace-href'; -import { Engine } from '@txtdot/sdk'; +import { Engine, EngineOutput, Middleware } from '@txtdot/sdk'; import { HandlerInput, HandlerOutput } from '@txtdot/sdk'; import config from './config'; import { parseHTML } from 'linkedom'; import { html2text } from './utils/html2text'; +import DOMPurify from 'isomorphic-dompurify'; interface IEngineId { [key: string]: number; @@ -18,14 +18,25 @@ interface IEngineId { export class Distributor { engines_id: IEngineId = {}; - fallback: Engine[] = []; - list: string[] = []; + engines_fallback: Engine[] = []; + engines_list: string[] = []; + + middles_id: IEngineId = {}; + middles_fallback: Middleware[] = []; + middles_list: string[] = []; + constructor() {} engine(engine: Engine) { - this.engines_id[engine.name] = this.list.length; - this.fallback.push(engine); - this.list.push(engine.name); + this.engines_id[engine.name] = this.engines_list.length; + this.engines_fallback.push(engine); + this.engines_list.push(engine.name); + } + + middleware(middleware: Middleware) { + this.middles_id[middleware.name] = this.middles_list.length; + this.middles_fallback.push(middleware); + this.middles_list.push(middleware.name); } async handlePage( @@ -52,22 +63,27 @@ export class Distributor { throw new NotHtmlMimetypeError(); } - const engine = this.getFallbackEngine(urlObj.hostname, engineName); - - const output = await engine.handle( - new HandlerInput( - await decodeStream(data, parseEncodingName(mime)), - remoteUrl - ) + const input = new HandlerInput( + await decodeStream(data, parseEncodingName(mime)), + remoteUrl ); + let output = await this.processEngines(urlObj.hostname, input, engineName); + + // Sanitize output before middlewares, because middlewares can add unsafe tags + output = { + ...output, + content: DOMPurify.sanitize(output.content), + }; + + output = await this.processMiddlewares(urlObj.hostname, input, output); + const dom = parseHTML(output.content); // Get text content before link replacement, because in text format we need original links const stdTextContent = dom.document.documentElement.textContent; // post-process - // TODO: generate dom in handler and not parse here twice replaceHref( dom.document, requestUrl, @@ -76,8 +92,6 @@ export class Distributor { redirectPath ); - const purify = DOMPurify(dom); - const content = purify.sanitize(dom.document.toString()); const title = output.title || dom.document.title; const lang = output.lang || dom.document.documentElement.lang; const textContent = @@ -85,24 +99,50 @@ export class Distributor { 'Text output cannot be generated.'; return { - content, + content: dom.document.toString(), textContent, title, lang, }; } - getFallbackEngine(host: string, specified?: string): Engine { + async processEngines( + host: string, + input: HandlerInput, + specified?: string + ): Promise { if (specified) { - return this.fallback[this.engines_id[specified]]; + return await this.engines_fallback[this.engines_id[specified]].handle( + input + ); } - for (const engine of this.fallback) { + for (const engine of this.engines_fallback) { if (micromatch.isMatch(host, engine.domains)) { - return engine; + try { + return await engine.handle(input); + } catch { + /*Try next engine*/ + } } } - return this.fallback[0]; + return await this.engines_fallback[0].handle(input); + } + + async processMiddlewares( + host: string, + input: HandlerInput, + output: EngineOutput + ): Promise { + let processed_output = output; + + for (const middle of this.middles_fallback) { + if (micromatch.isMatch(host, middle.domains)) { + processed_output = await middle.handle(input, processed_output); + } + } + + return processed_output; } } diff --git a/packages/server/src/plugin_manager.ts b/packages/server/src/plugin_manager.ts index 7eee703..9d8473c 100644 --- a/packages/server/src/plugin_manager.ts +++ b/packages/server/src/plugin_manager.ts @@ -7,5 +7,9 @@ for (const engine of plugin_config.engines) { distributor.engine(engine); } -export const engineList = distributor.list; +for (const middleware of plugin_config.middlewares || []) { + distributor.middleware(middleware); +} + +export const engineList = distributor.engines_list; export { distributor }; diff --git a/packages/server/src/routes/browser/configuration.ts b/packages/server/src/routes/browser/configuration.ts index e832d53..c78db2a 100644 --- a/packages/server/src/routes/browser/configuration.ts +++ b/packages/server/src/routes/browser/configuration.ts @@ -8,7 +8,8 @@ import config from '../../config'; export default async function configurationRoute(fastify: FastifyInstance) { fastify.get('/configuration', { schema: indexSchema }, async (_, reply) => { return reply.view('/templates/configuration.ejs', { - engines: distributor.fallback, + engines: distributor.engines_fallback, + middlewares: distributor.middles_fallback, config, }); }); diff --git a/packages/server/src/types/pluginConfig.ts b/packages/server/src/types/pluginConfig.ts index 634d48c..d2f0656 100644 --- a/packages/server/src/types/pluginConfig.ts +++ b/packages/server/src/types/pluginConfig.ts @@ -1,4 +1,4 @@ -import { Engine } from '@txtdot/sdk'; +import { Engine, Middleware } from '@txtdot/sdk'; type Html2TextConverter = (html: string) => string; @@ -7,6 +7,10 @@ export interface IAppConfig { * List of engines, ordered */ engines: Engine[]; + /** + * List of middlewares, ordered + */ + middlewares?: Middleware[]; /** * HTML to text converter, if engine doesn't support text */ diff --git a/packages/server/templates/configuration.ejs b/packages/server/templates/configuration.ejs index db0f7e4..58e206f 100644 --- a/packages/server/templates/configuration.ejs +++ b/packages/server/templates/configuration.ejs @@ -42,6 +42,14 @@ } %> +

Available middlewares

+
    + <% + for (const middleware of middlewares) { + %>
  1. <%= middleware.name %>: <%= middleware.description %>
  2. <% + } + %> +

Available routes

<% for (const route of config.dyn.routes) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66e2073..3f92435 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,9 +97,6 @@ importers: axios: specifier: ^1.6.8 version: 1.6.8 - dompurify: - specifier: ^3.1.2 - version: 3.1.3 dotenv: specifier: ^16.3.1 version: 16.4.5 @@ -115,6 +112,9 @@ importers: ip-range-check: specifier: ^0.2.0 version: 0.2.0 + isomorphic-dompurify: + specifier: ^2.10.0 + version: 2.10.0 json-schema-to-ts: specifier: ^3.0.1 version: 3.1.0 @@ -128,9 +128,6 @@ importers: specifier: ^0.33.3 version: 0.33.3 devDependencies: - '@types/dompurify': - specifier: ^3.0.5 - version: 3.0.5 '@types/ejs': specifier: ^3.1.5 version: 3.1.5 @@ -1149,10 +1146,18 @@ packages: cssom@0.5.0: resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + cssstyle@4.0.1: + resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + engines: {node: '>=18'} + dargs@7.0.0: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} engines: {node: '>=8'} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + dateformat@3.0.3: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} @@ -1176,6 +1181,9 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} @@ -1650,6 +1658,10 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -1826,6 +1838,9 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-ssh@1.4.0: resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} @@ -1862,6 +1877,10 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} + isomorphic-dompurify@2.10.0: + resolution: {integrity: sha512-rqWVh1tZtHL4NsSn+bifrxhZ0CqNNShGEkV+m2SFLL3d4D4jcIbkSVMva5Eep50uCxiCCNJmCLGbuAWY/QoNcQ==} + engines: {node: '>=18'} + jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} @@ -1897,6 +1916,15 @@ packages: jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + jsdom@24.0.0: + resolution: {integrity: sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2317,6 +2345,9 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nwsapi@2.2.10: + resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} + nx@18.3.4: resolution: {integrity: sha512-7rOHRyxpnZGJ3pHnwmpoAMHt9hNuwibWhOhPBJDhJVcbQJtGfwcWWyV/iSEnVXwKZ2lfHVE3TwE+gXFdT/GFiw==} hasBin: true @@ -2596,6 +2627,9 @@ packages: engines: {node: '>= 0.10'} hasBin: true + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -2603,6 +2637,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2693,6 +2730,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -2741,6 +2781,9 @@ packages: resolution: {integrity: sha512-nsii+MXoNb7NyF05LP9kaktx6AoBVT/7zUgDnzIb5IoYAvYkbZOAuoLJjVdsyEVxWv0swCxWkKDK4cMva+WDBA==} engines: {node: '>= 0.9'} + rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -2767,6 +2810,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -2969,6 +3016,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -3017,9 +3067,17 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} @@ -3113,6 +3171,10 @@ packages: universal-user-agent@6.0.1: resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -3128,6 +3190,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -3145,12 +3210,32 @@ packages: resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -3204,6 +3289,25 @@ packages: resolution: {integrity: sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==} engines: {node: '>=8'} + ws@8.17.0: + resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -4408,8 +4512,17 @@ snapshots: cssom@0.5.0: {} + cssstyle@4.0.1: + dependencies: + rrweb-cssom: 0.6.0 + dargs@7.0.0: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + dateformat@3.0.3: {} dateformat@4.6.3: {} @@ -4425,6 +4538,8 @@ snapshots: decamelize@1.2.0: {} + decimal.js@10.4.3: {} + dedent@0.7.0: {} deep-is@0.1.4: {} @@ -4944,6 +5059,10 @@ snapshots: dependencies: lru-cache: 10.2.2 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-escaper@3.0.3: {} html-to-text@9.0.5: @@ -5139,6 +5258,8 @@ snapshots: is-plain-object@5.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-ssh@1.4.0: dependencies: protocols: 2.0.1 @@ -5165,6 +5286,17 @@ snapshots: isobject@3.0.1: {} + isomorphic-dompurify@2.10.0: + dependencies: + '@types/dompurify': 3.0.5 + dompurify: 3.1.3 + jsdom: 24.0.0 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + jackspeak@2.3.6: dependencies: '@isaacs/cliui': 8.0.2 @@ -5202,6 +5334,34 @@ snapshots: jsbn@1.1.0: {} + jsdom@24.0.0: + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.4 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.10 + parse5: 7.1.2 + rrweb-cssom: 0.6.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.17.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + json-buffer@3.0.1: {} json-parse-better-errors@1.0.2: {} @@ -5779,6 +5939,8 @@ snapshots: dependencies: boolbase: 1.0.0 + nwsapi@2.2.10: {} + nx@18.3.4: dependencies: '@nrwl/tao': 18.3.4 @@ -6116,6 +6278,8 @@ snapshots: dependencies: event-stream: 3.3.4 + psl@1.9.0: {} + pump@3.0.0: dependencies: end-of-stream: 1.4.4 @@ -6123,6 +6287,8 @@ snapshots: punycode@2.3.1: {} + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -6232,6 +6398,8 @@ snapshots: require-from-string@2.0.2: {} + requires-port@1.0.0: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -6269,6 +6437,8 @@ snapshots: route-parser@0.0.5: {} + rrweb-cssom@0.6.0: {} + run-async@2.4.1: {} run-parallel@1.2.0: @@ -6291,6 +6461,10 @@ snapshots: safer-buffer@2.1.2: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + secure-json-parse@2.7.0: {} selderee@0.11.0: @@ -6513,6 +6687,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -6561,8 +6737,19 @@ snapshots: toidentifier@1.0.1: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + tr46@0.0.3: {} + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + trim-newlines@3.0.1: {} ts-algebra@2.0.0: {} @@ -6640,6 +6827,8 @@ snapshots: universal-user-agent@6.0.1: {} + universalify@0.2.0: {} + universalify@2.0.1: {} untildify@4.0.0: {} @@ -6650,6 +6839,11 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + util-deprecate@1.0.2: {} uuid@9.0.1: {} @@ -6667,12 +6861,29 @@ snapshots: dependencies: builtins: 5.1.0 + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + wcwidth@1.0.1: dependencies: defaults: 1.0.4 webidl-conversions@3.0.1: {} + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.0.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -6740,6 +6951,12 @@ snapshots: type-fest: 0.4.1 write-json-file: 3.2.0 + ws@8.17.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + xtend@4.0.2: {} y18n@5.0.8: {}