diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb6e81019..6a19412722 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - ### Server -- +- Enhance: メモリ使用量を削減しました ## 2025.11.1 diff --git a/packages/backend/package.json b/packages/backend/package.json index 95ebdbdd3c..f5ba86b1af 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -130,7 +130,6 @@ "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 +144,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", @@ -240,6 +240,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..c97e75ef2d 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 { + private async fetchDom(instance: MiInstance): Promise { 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 { + private async fetchFaviconUrl(instance: MiInstance, doc: htmlParser.HTMLElement | null): Promise { 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 | null): Promise { + private async fetchIconUrl(instance: MiInstance, doc: htmlParser.HTMLElement | null, manifest: Record | null): Promise { 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 === 'apple-touch-icon-precomposed')?.attributes.href, + links.find(link => link.attributes.rel === 'apple-touch-icon')?.attributes.href, + links.find(link => link.attributes.rel === '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 | null): Promise { + private async getThemeColor(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record | null): Promise { 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 | null): Promise { + private async getSiteName(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record | null): Promise { 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 | null): Promise { + private async getDescription(info: NodeInfo | null, doc: htmlParser.HTMLElement | null, manifest: Record | null): Promise { if (info && info.metadata) { if (typeof info.metadata.nodeDescription === 'string') { return info.metadata.nodeDescription; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 113a09cb14..68bd523810 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 { // 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 === 'me' && link.attributes.href === myLink); if (includesMyLink || includesRelMeLinks) { await this.userProfilesRepository.createQueryBuilder('profile').update() @@ -588,8 +587,6 @@ export default class extends Endpoint { // 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('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/pnpm-lock.yaml b/pnpm-lock.yaml index 61aaec6947..2133763d7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -273,9 +273,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 +315,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 @@ -598,6 +598,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 @@ -8669,6 +8672,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==} @@ -20199,6 +20205,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: {}