summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/web/ClientServerService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/web/ClientServerService.ts')
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts202
1 files changed, 86 insertions, 116 deletions
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index f9d904f3cd..bcea935409 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -9,21 +9,16 @@ import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import sharp from 'sharp';
-import pug from 'pug';
import { In, IsNull } from 'typeorm';
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';
import * as Acct from '@/misc/acct.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { PageEntityService } from '@/core/entities/PageEntityService.js';
-import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
@@ -42,14 +37,33 @@ import type {
} from '@/models/_.js';
import type Logger from '@/logger.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
+import { htmlSafeJsonStringify } from '@/misc/json-stringify-html-safe.js';
import { bindThis } from '@/decorators.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
-import { RoleService } from '@/core/RoleService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js';
import { ClientLoggerService } from './ClientLoggerService.js';
+import { HtmlTemplateService } from './HtmlTemplateService.js';
+
+import { BasePage } from './views/base.js';
+import { UserPage } from './views/user.js';
+import { NotePage } from './views/note.js';
+import { PagePage } from './views/page.js';
+import { ClipPage } from './views/clip.js';
+import { FlashPage } from './views/flash.js';
+import { GalleryPostPage } from './views/gallery-post.js';
+import { ChannelPage } from './views/channel.js';
+import { ReversiGamePage } from './views/reversi-game.js';
+import { AnnouncementPage } from './views/announcement.js';
+import { BaseEmbed } from './views/base-embed.js';
+import { InfoCardPage } from './views/info-card.js';
+import { BiosPage } from './views/bios.js';
+import { CliPage } from './views/cli.js';
+import { FlushPage } from './views/flush.js';
+import { ErrorPage } from './views/error.js';
+
import type { FastifyError, FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
const _filename = fileURLToPath(import.meta.url);
@@ -108,7 +122,6 @@ export class ClientServerService {
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private pageEntityService: PageEntityService,
- private metaEntityService: MetaEntityService,
private galleryPostEntityService: GalleryPostEntityService,
private clipEntityService: ClipEntityService,
private channelEntityService: ChannelEntityService,
@@ -116,7 +129,7 @@ export class ClientServerService {
private announcementEntityService: AnnouncementEntityService,
private urlPreviewService: UrlPreviewService,
private feedService: FeedService,
- private roleService: RoleService,
+ private htmlTemplateService: HtmlTemplateService,
private clientLoggerService: ClientLoggerService,
) {
//this.createServer = this.createServer.bind(this);
@@ -183,37 +196,9 @@ export class ClientServerService {
}
@bindThis
- private async generateCommonPugData(meta: MiMeta) {
- return {
- instanceName: meta.name ?? 'Misskey',
- icon: meta.iconUrl,
- appleTouchIcon: meta.app512IconUrl,
- themeColor: meta.themeColor,
- serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg',
- infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg',
- notFoundImageUrl: meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg',
- instanceUrl: this.config.url,
- metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)),
- now: Date.now(),
- federationEnabled: this.meta.federation !== 'none',
- };
- }
-
- @bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
const configUrl = new URL(this.config.url);
- fastify.register(fastifyView, {
- root: _dirname + '/views',
- engine: {
- pug: pug,
- },
- defaultContext: {
- version: this.config.version,
- config: this.config,
- },
- });
-
fastify.addHook('onRequest', (request, reply, done) => {
// クリックジャッキング防止のためiFrameの中に入れられないようにする
reply.header('X-Frame-Options', 'DENY');
@@ -414,16 +399,15 @@ export class ClientServerService {
//#endregion
- const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => {
+ const renderBase = async (reply: FastifyReply, data: Partial<Parameters<typeof BasePage>[0]> = {}) => {
reply.header('Cache-Control', 'public, max-age=30');
- return await reply.view('base', {
- img: this.meta.bannerUrl,
- url: this.config.url,
+ return await HtmlTemplateService.replyHtml(reply, BasePage({
+ img: this.meta.bannerUrl ?? undefined,
title: this.meta.name ?? 'Misskey',
- desc: this.meta.description,
- ...await this.generateCommonPugData(this.meta),
+ desc: this.meta.description ?? undefined,
+ ...await this.htmlTemplateService.getCommonData(),
...data,
- });
+ }));
};
// URL preview endpoint
@@ -505,11 +489,6 @@ export class ClientServerService {
)
) {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
- const me = profile.fields
- ? profile.fields
- .filter(filed => filed.value != null && filed.value.match(/^https?:/))
- .map(field => field.value)
- : [];
reply.header('Cache-Control', 'public, max-age=15');
if (profile.preventAiLearning) {
@@ -522,15 +501,15 @@ export class ClientServerService {
userProfile: profile,
});
- return await reply.view('user', {
- user, profile, me,
- avatarUrl: _user.avatarUrl,
+ return await HtmlTemplateService.replyHtml(reply, UserPage({
+ user: _user,
+ profile,
sub: request.params.sub,
- ...await this.generateCommonPugData(this.meta),
- clientCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ clientCtxJson: htmlSafeJsonStringify({
user: _user,
}),
- });
+ }));
} else {
// リモートユーザーなので
// モデレータがAPI経由で参照可能にするために404にはしない
@@ -581,17 +560,14 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('note', {
+ return await HtmlTemplateService.replyHtml(reply, NotePage({
note: _note,
profile,
- avatarUrl: _note.user.avatarUrl,
- // TODO: Let locale changeable by instance setting
- summary: getNoteSummary(_note),
- ...await this.generateCommonPugData(this.meta),
- clientCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ clientCtxJson: htmlSafeJsonStringify({
note: _note,
}),
- });
+ }));
} else {
return await renderBase(reply);
}
@@ -624,12 +600,11 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('page', {
+ return await HtmlTemplateService.replyHtml(reply, PagePage({
page: _page,
profile,
- avatarUrl: _page.user.avatarUrl,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -649,12 +624,11 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('flash', {
+ return await HtmlTemplateService.replyHtml(reply, FlashPage({
flash: _flash,
profile,
- avatarUrl: _flash.user.avatarUrl,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -674,15 +648,14 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('clip', {
+ return await HtmlTemplateService.replyHtml(reply, ClipPage({
clip: _clip,
profile,
- avatarUrl: _clip.user.avatarUrl,
- ...await this.generateCommonPugData(this.meta),
- clientCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ clientCtxJson: htmlSafeJsonStringify({
clip: _clip,
}),
- });
+ }));
} else {
return await renderBase(reply);
}
@@ -700,12 +673,11 @@ export class ClientServerService {
reply.header('X-Robots-Tag', 'noimageai');
reply.header('X-Robots-Tag', 'noai');
}
- return await reply.view('gallery-post', {
- post: _post,
+ return await HtmlTemplateService.replyHtml(reply, GalleryPostPage({
+ galleryPost: _post,
profile,
- avatarUrl: _post.user.avatarUrl,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -720,10 +692,10 @@ export class ClientServerService {
if (channel) {
const _channel = await this.channelEntityService.pack(channel);
reply.header('Cache-Control', 'public, max-age=15');
- return await reply.view('channel', {
+ return await HtmlTemplateService.replyHtml(reply, ChannelPage({
channel: _channel,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -738,10 +710,10 @@ export class ClientServerService {
if (game) {
const _game = await this.reversiGameEntityService.packDetail(game);
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('reversi-game', {
- game: _game,
- ...await this.generateCommonPugData(this.meta),
- });
+ return await HtmlTemplateService.replyHtml(reply, ReversiGamePage({
+ reversiGame: _game,
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -757,10 +729,10 @@ export class ClientServerService {
if (announcement) {
const _announcement = await this.announcementEntityService.pack(announcement);
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('announcement', {
+ return await HtmlTemplateService.replyHtml(reply, AnnouncementPage({
announcement: _announcement,
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
} else {
return await renderBase(reply);
}
@@ -793,13 +765,13 @@ export class ClientServerService {
const _user = await this.userEntityService.pack(user);
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('base-embed', {
+ return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
title: this.meta.name ?? 'Misskey',
- ...await this.generateCommonPugData(this.meta),
- embedCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ embedCtxJson: htmlSafeJsonStringify({
user: _user,
}),
- });
+ }));
});
fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
@@ -819,13 +791,13 @@ export class ClientServerService {
const _note = await this.noteEntityService.pack(note, null, { detail: true });
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('base-embed', {
+ return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
title: this.meta.name ?? 'Misskey',
- ...await this.generateCommonPugData(this.meta),
- embedCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ embedCtxJson: htmlSafeJsonStringify({
note: _note,
}),
- });
+ }));
});
fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => {
@@ -840,48 +812,46 @@ export class ClientServerService {
const _clip = await this.clipEntityService.pack(clip);
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('base-embed', {
+ return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
title: this.meta.name ?? 'Misskey',
- ...await this.generateCommonPugData(this.meta),
- embedCtx: htmlSafeJsonStringify({
+ ...await this.htmlTemplateService.getCommonData(),
+ embedCtxJson: htmlSafeJsonStringify({
clip: _clip,
}),
- });
+ }));
});
fastify.get('/embed/*', async (request, reply) => {
reply.removeHeader('X-Frame-Options');
reply.header('Cache-Control', 'public, max-age=3600');
- return await reply.view('base-embed', {
+ return await HtmlTemplateService.replyHtml(reply, BaseEmbed({
title: this.meta.name ?? 'Misskey',
- ...await this.generateCommonPugData(this.meta),
- });
+ ...await this.htmlTemplateService.getCommonData(),
+ }));
});
fastify.get('/_info_card_', async (request, reply) => {
reply.removeHeader('X-Frame-Options');
- return await reply.view('info-card', {
+ return await HtmlTemplateService.replyHtml(reply, InfoCardPage({
version: this.config.version,
- host: this.config.host,
+ config: this.config,
meta: this.meta,
- originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }),
- originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
- });
+ }));
});
//#endregion
fastify.get('/bios', async (request, reply) => {
- return await reply.view('bios', {
+ return await HtmlTemplateService.replyHtml(reply, BiosPage({
version: this.config.version,
- });
+ }));
});
fastify.get('/cli', async (request, reply) => {
- return await reply.view('cli', {
+ return await HtmlTemplateService.replyHtml(reply, CliPage({
version: this.config.version,
- });
+ }));
});
const override = (source: string, target: string, depth = 0) =>
@@ -904,7 +874,7 @@ export class ClientServerService {
reply.header('Clear-Site-Data', '"*"');
}
reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60');
- return await reply.view('flush');
+ return await HtmlTemplateService.replyHtml(reply, FlushPage());
});
// streamingに非WebSocketリクエストが来た場合にbase htmlをキャシュ付きで返すと、Proxy等でそのパスがキャッシュされておかしくなる
@@ -930,10 +900,10 @@ export class ClientServerService {
});
reply.code(500);
reply.header('Cache-Control', 'max-age=10, must-revalidate');
- return await reply.view('error', {
+ return await HtmlTemplateService.replyHtml(reply, ErrorPage({
code: error.code,
id: errId,
- });
+ }));
});
done();