summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--packages/backend/package.json6
-rw-r--r--packages/backend/src/core/FetchInstanceMetadataService.ts28
-rw-r--r--packages/backend/src/core/MfmService.ts185
-rw-r--r--packages/backend/src/core/activitypub/ApMfmService.ts8
-rw-r--r--packages/backend/src/core/activitypub/ApRendererService.ts26
-rw-r--r--packages/backend/src/core/activitypub/ApRequestService.ts28
-rw-r--r--packages/backend/src/misc/escape-html.ts13
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts11
-rw-r--r--packages/backend/src/server/oauth/OAuth2ProviderService.ts6
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts15
-rw-r--r--packages/backend/test/unit/ApMfmService.ts5
-rw-r--r--packages/backend/test/unit/MfmService.ts10
-rw-r--r--pnpm-lock.yaml114
14 files changed, 188 insertions, 270 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eeb6e81019..9567db9fa0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,8 @@
-
### Server
--
+- Enhance: メモリ使用量を削減しました
+- Enhance: ActivityPubアクティビティを送信する際のパフォーマンス向上
## 2025.11.1
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 95ebdbdd3c..edb8524330 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -121,16 +121,13 @@
"fluent-ffmpeg": "2.1.3",
"form-data": "4.0.5",
"got": "14.6.4",
- "happy-dom": "20.0.10",
"hpagent": "1.2.0",
- "htmlescape": "1.1.1",
"http-link-header": "1.1.3",
"ioredis": "5.8.2",
"ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0",
"is-svg": "5.1.0",
"js-yaml": "4.1.1",
- "jsdom": "26.1.0",
"json5": "2.2.3",
"jsonld": "8.3.3",
"jsrsasign": "11.1.0",
@@ -145,6 +142,7 @@
"nanoid": "5.1.6",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
+ "node-html-parser": "7.0.1",
"nodemailer": "7.0.10",
"nsfwjs": "4.2.0",
"oauth": "0.10.2",
@@ -201,7 +199,6 @@
"@types/color-convert": "2.0.4",
"@types/content-disposition": "0.5.9",
"@types/fluent-ffmpeg": "2.1.28",
- "@types/htmlescape": "1.1.3",
"@types/http-link-header": "1.0.7",
"@types/jest": "29.5.14",
"@types/js-yaml": "4.0.9",
@@ -240,6 +237,7 @@
"fkill": "9.0.0",
"jest": "29.7.0",
"jest-mock": "29.7.0",
+ "jsdom": "26.1.0",
"nodemon": "3.1.11",
"pid-port": "1.0.2",
"simple-oauth2": "5.1.0",
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index ce3af7c774..955f7035d7 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -5,9 +5,9 @@
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
-import { JSDOM } from 'jsdom';
import tinycolor from 'tinycolor2';
import * as Redis from 'ioredis';
+import * as htmlParser from 'node-html-parser';
import type { MiInstance } from '@/models/Instance.js';
import type Logger from '@/logger.js';
import { DI } from '@/di-symbols.js';
@@ -15,7 +15,6 @@ import { LoggerService } from '@/core/LoggerService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
-import type { DOMWindow } from 'jsdom';
type NodeInfo = {
openRegistrations?: unknown;
@@ -59,7 +58,7 @@ export class FetchInstanceMetadataService {
return await this.redisClient.set(
`fetchInstanceMetadata:mutex:v2:${host}`, '1',
'EX', 30, // 30秒したら自動でロック解除 https://github.com/misskey-dev/misskey/issues/13506#issuecomment-1975375395
- 'GET' // 古い値を返す(なかったらnull)
+ 'GET', // 古い値を返す(なかったらnull)
);
}
@@ -181,15 +180,14 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async fetchDom(instance: MiInstance): Promise<Document> {
+ private async fetchDom(instance: MiInstance): Promise<htmlParser.HTMLElement> {
this.logger.info(`Fetching HTML of ${instance.host} ...`);
const url = 'https://' + instance.host;
const html = await this.httpRequestService.getHtml(url);
- const { window } = new JSDOM(html);
- const doc = window.document;
+ const doc = htmlParser.parse(html);
return doc;
}
@@ -206,12 +204,12 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async fetchFaviconUrl(instance: MiInstance, doc: Document | null): Promise<string | null> {
+ private async fetchFaviconUrl(instance: MiInstance, doc: htmlParser.HTMLElement | null): Promise<string | null> {
const url = 'https://' + instance.host;
if (doc) {
// https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043
- const href = Array.from(doc.getElementsByTagName('link')).reverse().find(link => link.relList.contains('icon'))?.href;
+ const href = Array.from(doc.getElementsByTagName('link')).reverse().find(link => link.attributes.rel === 'icon')?.attributes.href;
if (href) {
return (new URL(href, url)).href;
@@ -232,7 +230,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async fetchIconUrl(instance: MiInstance, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
+ private async fetchIconUrl(instance: MiInstance, doc: htmlParser.HTMLElement | null, manifest: Record<string, any> | null): Promise<string | null> {
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
const url = 'https://' + instance.host;
return (new URL(manifest.icons[0].src, url)).href;
@@ -246,9 +244,9 @@ export class FetchInstanceMetadataService {
// https://github.com/misskey-dev/misskey/pull/8220/files/0ec4eba22a914e31b86874f12448f88b3e58dd5a#r796487559
const href =
[
- links.find(link => link.relList.contains('apple-touch-icon-precomposed'))?.href,
- links.find(link => link.relList.contains('apple-touch-icon'))?.href,
- links.find(link => link.relList.contains('icon'))?.href,
+ links.find(link => link.attributes.rel?.split(/\s+/).includes('apple-touch-icon-precomposed'))?.attributes.href,
+ links.find(link => link.attributes.rel?.split(/\s+/).includes('apple-touch-icon'))?.attributes.href,
+ links.find(link => link.attributes.rel?.split(/\s+/).includes('icon'))?.attributes.href,
]
.find(href => href);
@@ -261,7 +259,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async getThemeColor(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
+ private async getThemeColor(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record<string, any> | null): Promise<string | null> {
const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color;
if (themeColor) {
@@ -273,7 +271,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async getSiteName(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
+ private async getSiteName(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record<string, any> | null): Promise<string | null> {
if (info && info.metadata) {
if (typeof info.metadata.nodeName === 'string') {
return info.metadata.nodeName;
@@ -298,7 +296,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async getDescription(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
+ private async getDescription(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record<string, any> | null): Promise<string | null> {
if (info && info.metadata) {
if (typeof info.metadata.nodeDescription === 'string') {
return info.metadata.nodeDescription;
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 28d980f718..a359d5c838 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -6,13 +6,13 @@
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5';
-import { type Document, type HTMLParagraphElement, Window, XMLSerializer } from 'happy-dom';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
+import { escapeHtml } from '@/misc/escape-html.js';
import type { DefaultTreeAdapterMap } from 'parse5';
import type * as mfm from 'mfm-js';
@@ -23,8 +23,6 @@ type ChildNode = DefaultTreeAdapterMap['childNode'];
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
-export type Appender = (document: Document, body: HTMLParagraphElement) => void;
-
@Injectable()
export class MfmService {
constructor(
@@ -269,52 +267,35 @@ export class MfmService {
}
@bindThis
- public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], additionalAppenders: Appender[] = []) {
+ public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], extraHtml: string | null = null) {
if (nodes == null) {
return null;
}
- const { happyDOM, window } = new Window();
-
- const doc = window.document;
-
- const body = doc.createElement('p');
-
- function appendChildren(children: mfm.MfmNode[], targetElement: any): void {
- if (children) {
- for (const child of children.map(x => (handlers as any)[x.type](x))) targetElement.appendChild(child);
- }
+ function toHtml(children?: mfm.MfmNode[]): string {
+ if (children == null) return '';
+ return children.map(x => handlers[x.type](x)).join('');
}
function fnDefault(node: mfm.MfmFn) {
- const el = doc.createElement('i');
- appendChildren(node.children, el);
- return el;
+ return `<i>${toHtml(node.children)}</i>`;
}
- const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any } = {
+ const handlers = {
bold: (node) => {
- const el = doc.createElement('b');
- appendChildren(node.children, el);
- return el;
+ return `<b>${toHtml(node.children)}</b>`;
},
small: (node) => {
- const el = doc.createElement('small');
- appendChildren(node.children, el);
- return el;
+ return `<small>${toHtml(node.children)}</small>`;
},
strike: (node) => {
- const el = doc.createElement('del');
- appendChildren(node.children, el);
- return el;
+ return `<del>${toHtml(node.children)}</del>`;
},
italic: (node) => {
- const el = doc.createElement('i');
- appendChildren(node.children, el);
- return el;
+ return `<i>${toHtml(node.children)}</i>`;
},
fn: (node) => {
@@ -323,10 +304,7 @@ export class MfmService {
const text = node.children[0].type === 'text' ? node.children[0].props.text : '';
try {
const date = new Date(parseInt(text, 10) * 1000);
- const el = doc.createElement('time');
- el.setAttribute('datetime', date.toISOString());
- el.textContent = date.toISOString();
- return el;
+ return `<time datetime="${escapeHtml(date.toISOString())}">${escapeHtml(date.toISOString())}</time>`;
} catch (err) {
return fnDefault(node);
}
@@ -336,21 +314,9 @@ export class MfmService {
if (node.children.length === 1) {
const child = node.children[0];
const text = child.type === 'text' ? child.props.text : '';
- const rubyEl = doc.createElement('ruby');
- const rtEl = doc.createElement('rt');
-
- // ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする
- const rpStartEl = doc.createElement('rp');
- rpStartEl.appendChild(doc.createTextNode('('));
- const rpEndEl = doc.createElement('rp');
- rpEndEl.appendChild(doc.createTextNode(')'));
- rubyEl.appendChild(doc.createTextNode(text.split(' ')[0]));
- rtEl.appendChild(doc.createTextNode(text.split(' ')[1]));
- rubyEl.appendChild(rpStartEl);
- rubyEl.appendChild(rtEl);
- rubyEl.appendChild(rpEndEl);
- return rubyEl;
+ // ruby未対応のHTMLサニタイザーを通したときにルビが「対象テキスト(ルビテキスト)」にフォールバックするようにする
+ return `<ruby>${escapeHtml(text.split(' ')[0])}<rp>(</rp><rt>${escapeHtml(text.split(' ')[1])}</rt><rp>)</rp></ruby>`;
} else {
const rt = node.children.at(-1);
@@ -359,21 +325,9 @@ export class MfmService {
}
const text = rt.type === 'text' ? rt.props.text : '';
- const rubyEl = doc.createElement('ruby');
- const rtEl = doc.createElement('rt');
- // ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする
- const rpStartEl = doc.createElement('rp');
- rpStartEl.appendChild(doc.createTextNode('('));
- const rpEndEl = doc.createElement('rp');
- rpEndEl.appendChild(doc.createTextNode(')'));
-
- appendChildren(node.children.slice(0, node.children.length - 1), rubyEl);
- rtEl.appendChild(doc.createTextNode(text.trim()));
- rubyEl.appendChild(rpStartEl);
- rubyEl.appendChild(rtEl);
- rubyEl.appendChild(rpEndEl);
- return rubyEl;
+ // ruby未対応のHTMLサニタイザーを通したときにルビが「対象テキスト(ルビテキスト)」にフォールバックするようにする
+ return `<ruby>${toHtml(node.children.slice(0, node.children.length - 1))}<rp>(</rp><rt>${escapeHtml(text.trim())}</rt><rp>)</rp></ruby>`;
}
}
@@ -384,125 +338,98 @@ export class MfmService {
},
blockCode: (node) => {
- const pre = doc.createElement('pre');
- const inner = doc.createElement('code');
- inner.textContent = node.props.code;
- pre.appendChild(inner);
- return pre;
+ return `<pre><code>${escapeHtml(node.props.code)}</code></pre>`;
},
center: (node) => {
- const el = doc.createElement('div');
- appendChildren(node.children, el);
- return el;
+ return `<div style="text-align: center;">${toHtml(node.children)}</div>`;
},
emojiCode: (node) => {
- return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
+ return `\u200B:${escapeHtml(node.props.name)}:\u200B`;
},
unicodeEmoji: (node) => {
- return doc.createTextNode(node.props.emoji);
+ return node.props.emoji;
},
hashtag: (node) => {
- const a = doc.createElement('a');
- a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`);
- a.textContent = `#${node.props.hashtag}`;
- a.setAttribute('rel', 'tag');
- return a;
+ return `<a href="${escapeHtml(`${this.config.url}/tags/${encodeURIComponent(node.props.hashtag)}`)}" rel="tag">#${escapeHtml(node.props.hashtag)}</a>`;
},
inlineCode: (node) => {
- const el = doc.createElement('code');
- el.textContent = node.props.code;
- return el;
+ return `<code>${escapeHtml(node.props.code)}</code>`;
},
mathInline: (node) => {
- const el = doc.createElement('code');
- el.textContent = node.props.formula;
- return el;
+ return `<code>${escapeHtml(node.props.formula)}</code>`;
},
mathBlock: (node) => {
- const el = doc.createElement('code');
- el.textContent = node.props.formula;
- return el;
+ return `<pre><code>${escapeHtml(node.props.formula)}</code></pre>`;
},
link: (node) => {
- const a = doc.createElement('a');
- a.setAttribute('href', node.props.url);
- appendChildren(node.children, a);
- return a;
+ try {
+ const url = new URL(node.props.url);
+ return `<a href="${escapeHtml(url.href)}">${toHtml(node.children)}</a>`;
+ } catch (err) {
+ return `[${toHtml(node.children)}](${escapeHtml(node.props.url)})`;
+ }
},
mention: (node) => {
- const a = doc.createElement('a');
const { username, host, acct } = node.props;
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username.toLowerCase() === username.toLowerCase() && remoteUser.host?.toLowerCase() === host?.toLowerCase());
- a.setAttribute('href', remoteUserInfo
+ const href = remoteUserInfo
? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri)
- : `${this.config.url}/${acct.endsWith(`@${this.config.url}`) ? acct.substring(0, acct.length - this.config.url.length - 1) : acct}`);
- a.className = 'u-url mention';
- a.textContent = acct;
- return a;
+ : `${this.config.url}/${acct.endsWith(`@${this.config.url}`) ? acct.substring(0, acct.length - this.config.url.length - 1) : acct}`;
+ try {
+ const url = new URL(href);
+ return `<a href="${escapeHtml(url.href)}" class="u-url mention">${escapeHtml(acct)}</a>`;
+ } catch (err) {
+ return escapeHtml(acct);
+ }
},
quote: (node) => {
- const el = doc.createElement('blockquote');
- appendChildren(node.children, el);
- return el;
+ return `<blockquote>${toHtml(node.children)}</blockquote>`;
},
text: (node) => {
if (!node.props.text.match(/[\r\n]/)) {
- return doc.createTextNode(node.props.text);
+ return escapeHtml(node.props.text);
}
- const el = doc.createElement('span');
- const nodes = node.props.text.split(/\r\n|\r|\n/).map(x => doc.createTextNode(x));
+ let html = '';
+
+ const lines = node.props.text.split(/\r\n|\r|\n/).map(x => escapeHtml(x));
- for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
- el.appendChild(x === 'br' ? doc.createElement('br') : x);
+ for (const x of intersperse<FIXME | 'br'>('br', lines)) {
+ html += x === 'br' ? '<br />' : x;
}
- return el;
+ return html;
},
url: (node) => {
- const a = doc.createElement('a');
- a.setAttribute('href', node.props.url);
- a.textContent = node.props.url;
- return a;
+ try {
+ const url = new URL(node.props.url);
+ return `<a href="${escapeHtml(url.href)}">${escapeHtml(node.props.url)}</a>`;
+ } catch (err) {
+ return escapeHtml(node.props.url);
+ }
},
search: (node) => {
- const a = doc.createElement('a');
- a.setAttribute('href', `https://www.google.com/search?q=${node.props.query}`);
- a.textContent = node.props.content;
- return a;
+ return `<a href="${escapeHtml(`https://www.google.com/search?q=${encodeURIComponent(node.props.query)}`)}">${escapeHtml(node.props.content)}</a>`;
},
plain: (node) => {
- const el = doc.createElement('span');
- appendChildren(node.children, el);
- return el;
+ return `<span>${toHtml(node.children)}</span>`;
},
- };
-
- appendChildren(nodes, body);
-
- for (const additionalAppender of additionalAppenders) {
- additionalAppender(doc, body);
- }
-
- // Remove the unnecessary namespace
- const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*<p xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">/, '<p>');
-
- happyDOM.close().catch(err => {});
+ } satisfies { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => string } as { [K in mfm.MfmNode['type']]: (node: mfm.MfmNode) => string };
- return serialized;
+ return `${toHtml(nodes)}${extraHtml ?? ''}`;
}
}
diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts
index f4c07e472c..a928ed5ccf 100644
--- a/packages/backend/src/core/activitypub/ApMfmService.ts
+++ b/packages/backend/src/core/activitypub/ApMfmService.ts
@@ -5,7 +5,7 @@
import { Injectable } from '@nestjs/common';
import * as mfm from 'mfm-js';
-import { MfmService, Appender } from '@/core/MfmService.js';
+import { MfmService } from '@/core/MfmService.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import { extractApHashtagObjects } from './models/tag.js';
@@ -25,17 +25,17 @@ export class ApMfmService {
}
@bindThis
- public getNoteHtml(note: Pick<MiNote, 'text' | 'mentionedRemoteUsers'>, additionalAppender: Appender[] = []) {
+ public getNoteHtml(note: Pick<MiNote, 'text' | 'mentionedRemoteUsers'>, extraHtml: string | null = null) {
let noMisskeyContent = false;
const srcMfm = (note.text ?? '');
const parsed = mfm.parse(srcMfm);
- if (!additionalAppender.length && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
+ if (extraHtml == null && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
noMisskeyContent = true;
}
- const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers), additionalAppender);
+ const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers), extraHtml);
return {
content,
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 55521d6e3a..4570977c5d 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -19,7 +19,7 @@ import type { MiEmoji } from '@/models/Emoji.js';
import type { MiPoll } from '@/models/Poll.js';
import type { MiPollVote } from '@/models/PollVote.js';
import { UserKeypairService } from '@/core/UserKeypairService.js';
-import { MfmService, type Appender } from '@/core/MfmService.js';
+import { MfmService } from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { MiUserKeypair } from '@/models/UserKeypair.js';
@@ -28,6 +28,7 @@ import { bindThis } from '@/decorators.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { IdService } from '@/core/IdService.js';
import { UtilityService } from '@/core/UtilityService.js';
+import { escapeHtml } from '@/misc/escape-html.js';
import { JsonLdService } from './JsonLdService.js';
import { ApMfmService } from './ApMfmService.js';
import { CONTEXT } from './misc/contexts.js';
@@ -384,7 +385,7 @@ export class ApRendererService {
inReplyTo = null;
}
- let quote;
+ let quote: string | undefined;
if (note.renoteId) {
const renote = await this.notesRepository.findOneBy({ id: note.renoteId });
@@ -430,29 +431,18 @@ export class ApRendererService {
poll = await this.pollsRepository.findOneBy({ noteId: note.id });
}
- const apAppend: Appender[] = [];
+ let extraHtml: string | null = null;
- if (quote) {
+ if (quote != null) {
// Append quote link as `<br><br><span class="quote-inline">RE: <a href="...">...</a></span>`
- // the claas name `quote-inline` is used in non-misskey clients for styling quote notes.
+ // the class name `quote-inline` is used in non-misskey clients for styling quote notes.
// For compatibility, the span part should be kept as possible.
- apAppend.push((doc, body) => {
- body.appendChild(doc.createElement('br'));
- body.appendChild(doc.createElement('br'));
- const span = doc.createElement('span');
- span.className = 'quote-inline';
- span.appendChild(doc.createTextNode('RE: '));
- const link = doc.createElement('a');
- link.setAttribute('href', quote);
- link.textContent = quote;
- span.appendChild(link);
- body.appendChild(span);
- });
+ extraHtml = `<br><br><span class="quote-inline">RE: <a href="${escapeHtml(quote)}">${escapeHtml(quote)}</a></span>`;
}
const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
- const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend);
+ const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, extraHtml);
const emojis = await this.getEmojis(note.emojis);
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts
index 61d328ccac..49298a1d22 100644
--- a/packages/backend/src/core/activitypub/ApRequestService.ts
+++ b/packages/backend/src/core/activitypub/ApRequestService.ts
@@ -6,7 +6,7 @@
import * as crypto from 'node:crypto';
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
-import { Window } from 'happy-dom';
+import * as htmlParser from 'node-html-parser';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { MiUser } from '@/models/User.js';
@@ -215,29 +215,9 @@ export class ApRequestService {
_followAlternate === true
) {
const html = await res.text();
- const { window, happyDOM } = new Window({
- settings: {
- disableJavaScriptEvaluation: true,
- disableJavaScriptFileLoading: true,
- disableCSSFileLoading: true,
- disableComputedStyleRendering: true,
- handleDisabledFileLoadingAsSuccess: true,
- navigation: {
- disableMainFrameNavigation: true,
- disableChildFrameNavigation: true,
- disableChildPageNavigation: true,
- disableFallbackToSetURL: true,
- },
- timer: {
- maxTimeout: 0,
- maxIntervalTime: 0,
- maxIntervalIterations: 0,
- },
- },
- });
- const document = window.document;
+
try {
- document.documentElement.innerHTML = html;
+ const document = htmlParser.parse(html);
const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]');
if (alternate) {
@@ -248,8 +228,6 @@ export class ApRequestService {
}
} catch (e) {
// something went wrong parsing the HTML, ignore the whole thing
- } finally {
- happyDOM.close().catch(err => {});
}
}
//#endregion
diff --git a/packages/backend/src/misc/escape-html.ts b/packages/backend/src/misc/escape-html.ts
new file mode 100644
index 0000000000..819aeeed52
--- /dev/null
+++ b/packages/backend/src/misc/escape-html.ts
@@ -0,0 +1,13 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export function escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#039;');
+}
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index 113a09cb14..9971a1ea4d 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -7,7 +7,7 @@ import RE2 from 're2';
import * as mfm from 'mfm-js';
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
-import { JSDOM } from 'jsdom';
+import * as htmlParser from 'node-html-parser';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
import * as Acct from '@/misc/acct.js';
@@ -569,16 +569,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
try {
const html = await this.httpRequestService.getHtml(url);
- const { window } = new JSDOM(html);
- const doc: Document = window.document;
+ const doc = htmlParser.parse(html);
const myLink = `${this.config.url}/@${user.username}`;
const aEls = Array.from(doc.getElementsByTagName('a'));
const linkEls = Array.from(doc.getElementsByTagName('link'));
- const includesMyLink = aEls.some(a => a.href === myLink);
- const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.rel === 'me' && link.href === myLink);
+ const includesMyLink = aEls.some(a => a.attributes.href === myLink);
+ const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.attributes.rel?.split(/\s+/).includes('me') && link.attributes.href === myLink);
if (includesMyLink || includesRelMeLinks) {
await this.userProfilesRepository.createQueryBuilder('profile').update()
@@ -588,8 +587,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
})
.execute();
}
-
- window.close();
} catch (err) {
// なにもしない
}
diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
index cdd7102666..102998e8be 100644
--- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts
+++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
@@ -6,7 +6,7 @@
import dns from 'node:dns/promises';
import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
-import { JSDOM } from 'jsdom';
+import * as htmlParser from 'node-html-parser';
import httpLinkHeader from 'http-link-header';
import ipaddr from 'ipaddr.js';
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize';
@@ -120,9 +120,9 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt
}
const text = await res.text();
- const fragment = JSDOM.fragment(text);
+ const fragment = htmlParser.parse(text);
- redirectUris.push(...[...fragment.querySelectorAll<HTMLLinkElement>('link[rel=redirect_uri][href]')].map(el => el.href));
+ redirectUris.push(...[...fragment.querySelectorAll('link[rel=redirect_uri][href]')].map(el => el.attributes.href));
let name = id;
let logo: string | null = null;
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index f9d904f3cd..fef6a27087 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -15,7 +15,6 @@ import fastifyStatic from '@fastify/static';
import fastifyView from '@fastify/view';
import fastifyProxy from '@fastify/http-proxy';
import vary from 'vary';
-import htmlSafeJsonStringify from 'htmlescape';
import type { Config } from '@/config.js';
import { getNoteSummary } from '@/misc/get-note-summary.js';
import { DI } from '@/di-symbols.js';
@@ -63,6 +62,20 @@ const frontendViteOut = `${_dirname}/../../../../../built/_frontend_vite_/`;
const frontendEmbedViteOut = `${_dirname}/../../../../../built/_frontend_embed_vite_/`;
const tarball = `${_dirname}/../../../../../built/tarball/`;
+const ESCAPE_LOOKUP = {
+ '&': '\\u0026',
+ '>': '\\u003e',
+ '<': '\\u003c',
+ '\u2028': '\\u2028',
+ '\u2029': '\\u2029',
+} as Record<string, string>;
+
+const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
+
+function htmlSafeJsonStringify(obj: any): string {
+ return JSON.stringify(obj).replace(ESCAPE_REGEX, x => ESCAPE_LOOKUP[x]);
+}
+
@Injectable()
export class ClientServerService {
private logger: Logger;
diff --git a/packages/backend/test/unit/ApMfmService.ts b/packages/backend/test/unit/ApMfmService.ts
index e81a321c9b..93efa5d7d3 100644
--- a/packages/backend/test/unit/ApMfmService.ts
+++ b/packages/backend/test/unit/ApMfmService.ts
@@ -9,7 +9,6 @@ import { Test } from '@nestjs/testing';
import { CoreModule } from '@/core/CoreModule.js';
import { ApMfmService } from '@/core/activitypub/ApMfmService.js';
import { GlobalModule } from '@/GlobalModule.js';
-import { MiNote } from '@/models/Note.js';
describe('ApMfmService', () => {
let apMfmService: ApMfmService;
@@ -31,7 +30,7 @@ describe('ApMfmService', () => {
const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
assert.equal(noMisskeyContent, true, 'noMisskeyContent');
- assert.equal(content, '<p>テキスト <a href="http://misskey.local/tags/タグ" rel="tag">#タグ</a> <a href="http://misskey.local/@mention" class="u-url mention">@mention</a> 🍊 ​:emoji:​ <a href="https://example.com">https://example.com</a></p>', 'content');
+ assert.equal(content, 'テキスト <a href="http://misskey.local/tags/%E3%82%BF%E3%82%B0" rel="tag">#タグ</a> <a href="http://misskey.local/@mention" class="u-url mention">@mention</a> 🍊 ​:emoji:​ <a href="https://example.com/">https://example.com</a>', 'content');
});
test('Provide _misskey_content for MFM', () => {
@@ -43,7 +42,7 @@ describe('ApMfmService', () => {
const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
assert.equal(noMisskeyContent, false, 'noMisskeyContent');
- assert.equal(content, '<p><i>foo</i></p>', 'content');
+ assert.equal(content, '<i>foo</i>', 'content');
});
});
});
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index 7350da3cae..2f5f3745de 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -24,25 +24,25 @@ describe('MfmService', () => {
describe('toHtml', () => {
test('br', () => {
const input = 'foo\nbar\nbaz';
- const output = '<p><span>foo<br />bar<br />baz</span></p>';
+ const output = 'foo<br />bar<br />baz';
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
});
test('br alt', () => {
const input = 'foo\r\nbar\rbaz';
- const output = '<p><span>foo<br />bar<br />baz</span></p>';
+ const output = 'foo<br />bar<br />baz';
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
});
test('Do not generate unnecessary span', () => {
const input = 'foo $[tada bar]';
- const output = '<p>foo <i>bar</i></p>';
+ const output = 'foo <i>bar</i>';
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
});
test('escape', () => {
const input = '```\n<p>Hello, world!</p>\n```';
- const output = '<p><pre><code>&lt;p&gt;Hello, world!&lt;/p&gt;</code></pre></p>';
+ const output = '<pre><code>&lt;p&gt;Hello, world!&lt;/p&gt;</code></pre>';
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
});
});
@@ -118,7 +118,7 @@ describe('MfmService', () => {
assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミス キー</rt><rp>)</rp> b</ruby> c</p>'), 'a Misskey(ミス キー) b c');
assert.deepStrictEqual(
mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp>Misskey<rp>(</rp><rt>ミス キー</rt><rp>)</rp>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp></ruby> b</p>'),
- 'a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b'
+ 'a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b',
);
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 61aaec6947..0317a4a6f4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -246,15 +246,9 @@ importers:
got:
specifier: 14.6.4
version: 14.6.4
- happy-dom:
- specifier: 20.0.10
- version: 20.0.10
hpagent:
specifier: 1.2.0
version: 1.2.0
- htmlescape:
- specifier: 1.1.1
- version: 1.1.1
http-link-header:
specifier: 1.1.3
version: 1.1.3
@@ -273,9 +267,6 @@ importers:
js-yaml:
specifier: 4.1.1
version: 4.1.1
- jsdom:
- specifier: 26.1.0
- version: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
json5:
specifier: 2.2.3
version: 2.2.3
@@ -318,6 +309,9 @@ importers:
node-fetch:
specifier: 3.3.2
version: 3.3.2
+ node-html-parser:
+ specifier: 7.0.1
+ version: 7.0.1
nodemailer:
specifier: 7.0.10
version: 7.0.10
@@ -481,9 +475,6 @@ importers:
'@types/fluent-ffmpeg':
specifier: 2.1.28
version: 2.1.28
- '@types/htmlescape':
- specifier: 1.1.3
- version: 1.1.3
'@types/http-link-header':
specifier: 1.0.7
version: 1.0.7
@@ -598,6 +589,9 @@ importers:
jest-mock:
specifier: 29.7.0
version: 29.7.0
+ jsdom:
+ specifier: 26.1.0
+ version: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
nodemon:
specifier: 3.1.11
version: 3.1.11
@@ -4671,9 +4665,6 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
- '@types/htmlescape@1.1.3':
- resolution: {integrity: sha512-tuC81YJXGUe0q8WRtBNW+uyx79rkkzWK651ALIXXYq5/u/IxjX4iHneGF2uUqzsNp+F+9J2mFZOv9jiLTtIq0w==}
-
'@types/http-cache-semantics@4.0.4':
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
@@ -7280,10 +7271,6 @@ packages:
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
- htmlescape@1.1.1:
- resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==}
- engines: {node: '>=0.10'}
-
htmlparser2@10.0.0:
resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==}
@@ -8669,6 +8656,9 @@ packages:
engines: {node: ^18.17.0 || >=20.5.0}
hasBin: true
+ node-html-parser@7.0.1:
+ resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==}
+
node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
@@ -11407,7 +11397,7 @@ snapshots:
'@apm-js-collab/tracing-hooks@0.3.1':
dependencies:
'@apm-js-collab/code-transformer': 0.8.2
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
module-details-from-path: 1.0.4
transitivePeerDependencies:
- supports-color
@@ -11997,7 +11987,7 @@ snapshots:
'@babel/types': 7.28.5
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -12156,7 +12146,7 @@ snapshots:
'@babel/parser': 7.28.5
'@babel/template': 7.27.2
'@babel/types': 7.28.5
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -12486,7 +12476,7 @@ snapshots:
'@eslint/config-array@0.21.1':
dependencies:
'@eslint/object-schema': 2.1.7
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -12506,7 +12496,7 @@ snapshots:
'@eslint/eslintrc@3.3.1':
dependencies:
ajv: 6.12.6
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
@@ -13341,7 +13331,7 @@ snapshots:
dependencies:
agent-base: 7.1.4
http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6(supports-color@10.2.2)
+ https-proxy-agent: 7.0.6
lru-cache: 10.4.3
socks-proxy-agent: 8.0.5
transitivePeerDependencies:
@@ -15011,7 +15001,7 @@ snapshots:
'@tokenizer/inflate@0.2.7':
dependencies:
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
fflate: 0.8.2
token-types: 6.1.1
transitivePeerDependencies:
@@ -15019,7 +15009,7 @@ snapshots:
'@tokenizer/inflate@0.3.1':
dependencies:
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
fflate: 0.8.2
token-types: 6.1.1
transitivePeerDependencies:
@@ -15027,7 +15017,7 @@ snapshots:
'@tokenizer/inflate@0.4.1':
dependencies:
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
token-types: 6.1.1
transitivePeerDependencies:
- supports-color
@@ -15153,8 +15143,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
- '@types/htmlescape@1.1.3': {}
-
'@types/http-cache-semantics@4.0.4': {}
'@types/http-errors@2.0.5': {}
@@ -15432,7 +15420,7 @@ snapshots:
'@typescript-eslint/types': 8.47.0
'@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.47.0
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
eslint: 9.39.1
typescript: 5.9.3
transitivePeerDependencies:
@@ -15442,7 +15430,7 @@ snapshots:
dependencies:
'@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3)
'@typescript-eslint/types': 8.47.0
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -15461,7 +15449,7 @@ snapshots:
'@typescript-eslint/types': 8.47.0
'@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.47.0(eslint@9.39.1)(typescript@5.9.3)
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
eslint: 9.39.1
ts-api-utils: 2.1.0(typescript@5.9.3)
typescript: 5.9.3
@@ -15476,7 +15464,7 @@ snapshots:
'@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3)
'@typescript-eslint/types': 8.47.0
'@typescript-eslint/visitor-keys': 8.47.0
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
@@ -15933,7 +15921,7 @@ snapshots:
agent-base@6.0.2:
dependencies:
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
optional: true
@@ -16230,7 +16218,7 @@ snapshots:
axios@0.24.0:
dependencies:
- follow-redirects: 1.15.11(debug@4.4.3)
+ follow-redirects: 1.15.11
transitivePeerDependencies:
- debug
@@ -17059,6 +17047,10 @@ snapshots:
dependencies:
ms: 2.0.0
+ debug@3.2.7:
+ dependencies:
+ ms: 2.1.3
+
debug@3.2.7(supports-color@8.1.1):
dependencies:
ms: 2.1.3
@@ -17547,7 +17539,7 @@ snapshots:
eslint-import-resolver-node@0.3.9:
dependencies:
- debug: 3.2.7(supports-color@8.1.1)
+ debug: 3.2.7
is-core-module: 2.16.1
resolve: 1.22.11
transitivePeerDependencies:
@@ -17555,7 +17547,7 @@ snapshots:
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1):
dependencies:
- debug: 3.2.7(supports-color@8.1.1)
+ debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.47.0(eslint@9.39.1)(typescript@5.9.3)
eslint: 9.39.1
@@ -17570,7 +17562,7 @@ snapshots:
array.prototype.findlastindex: 1.2.6
array.prototype.flat: 1.3.3
array.prototype.flatmap: 1.3.3
- debug: 3.2.7(supports-color@8.1.1)
+ debug: 3.2.7
doctrine: 2.1.0
eslint: 9.39.1
eslint-import-resolver-node: 0.3.9
@@ -17634,7 +17626,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -18064,6 +18056,8 @@ snapshots:
async: 0.2.10
which: 1.3.1
+ follow-redirects@1.15.11: {}
+
follow-redirects@1.15.11(debug@4.4.3):
optionalDependencies:
debug: 4.4.3(supports-color@10.2.2)
@@ -18416,8 +18410,6 @@ snapshots:
html-void-elements@3.0.0: {}
- htmlescape@1.1.1: {}
-
htmlparser2@10.0.0:
dependencies:
domelementtype: 2.3.0
@@ -18461,7 +18453,7 @@ snapshots:
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -18481,7 +18473,7 @@ snapshots:
https-proxy-agent@2.2.4:
dependencies:
agent-base: 4.3.0
- debug: 3.2.7(supports-color@8.1.1)
+ debug: 3.2.7
transitivePeerDependencies:
- supports-color
optional: true
@@ -18489,11 +18481,18 @@ snapshots:
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
optional: true
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3(supports-color@5.5.0)
+ transitivePeerDependencies:
+ - supports-color
+
https-proxy-agent@7.0.6(supports-color@10.2.2):
dependencies:
agent-base: 7.1.4
@@ -18597,7 +18596,7 @@ snapshots:
dependencies:
'@ioredis/commands': 1.4.0
cluster-key-slot: 1.1.2
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
@@ -18837,7 +18836,7 @@ snapshots:
istanbul-lib-source-maps@4.0.1:
dependencies:
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
istanbul-lib-coverage: 3.2.2
source-map: 0.6.1
transitivePeerDependencies:
@@ -19238,7 +19237,7 @@ snapshots:
decimal.js: 10.6.0
html-encoding-sniffer: 4.0.0
http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6(supports-color@10.2.2)
+ https-proxy-agent: 7.0.6
is-potential-custom-element-name: 1.0.1
nwsapi: 2.2.22
parse5: 7.3.0
@@ -20125,7 +20124,7 @@ snapshots:
needle@2.9.1:
dependencies:
- debug: 3.2.7(supports-color@8.1.1)
+ debug: 3.2.7
iconv-lite: 0.4.24
sax: 1.4.3
transitivePeerDependencies:
@@ -20199,6 +20198,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ node-html-parser@7.0.1:
+ dependencies:
+ css-select: 5.2.2
+ he: 1.2.0
+
node-int64@0.4.0: {}
node-releases@2.0.27: {}
@@ -21241,7 +21245,7 @@ snapshots:
require-in-the-middle@7.5.2:
dependencies:
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
module-details-from-path: 1.0.4
resolve: 1.22.11
transitivePeerDependencies:
@@ -21575,7 +21579,7 @@ snapshots:
dependencies:
'@hapi/hoek': 11.0.7
'@hapi/wreck': 18.1.0
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
joi: 17.13.3
transitivePeerDependencies:
- supports-color
@@ -21675,7 +21679,7 @@ snapshots:
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.4
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
socks: 2.8.7
transitivePeerDependencies:
- supports-color
@@ -21970,7 +21974,7 @@ snapshots:
dependencies:
component-emitter: 1.3.1
cookiejar: 2.1.4
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
fast-safe-stringify: 2.1.1
form-data: 4.0.5
formidable: 3.5.4
@@ -22318,7 +22322,7 @@ snapshots:
app-root-path: 3.1.0
buffer: 6.0.3
dayjs: 1.11.19
- debug: 4.4.3(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@5.5.0)
dedent: 1.7.0
dotenv: 16.6.1
glob: 10.5.0
@@ -22760,7 +22764,7 @@ snapshots:
dependencies:
asn1.js: 5.4.1
http_ece: 1.2.0
- https-proxy-agent: 7.0.6(supports-color@10.2.2)
+ https-proxy-agent: 7.0.6
jws: 4.0.0
minimist: 1.2.8
transitivePeerDependencies: