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}${name}>`;
+ return `<${name} ${propsstr}>${content}${name}>`;
} 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) {
+ %>- <%= middleware.name %>: <%= middleware.description %>
<%
+ }
+ %>
+
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: {}