{
if (children) {
for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child);
@@ -656,7 +658,7 @@ export class MfmService {
},
};
- await appendChildren(nodes, doc.body);
+ await appendChildren(nodes, body);
if (quoteUri !== null) {
const a = doc.createElement('a');
@@ -670,9 +672,15 @@ export class MfmService {
quote.innerHTML += 'RE: ';
quote.appendChild(a);
- doc.body.appendChild(quote);
+ body.appendChild(quote);
}
- return inline ? doc.body.innerHTML : `${doc.body.innerHTML}
`;
+ let result = new XMLSerializer().serializeToString(body);
+
+ if (inline) {
+ result = result.replace(/^/,'').replace(/<\/p>$/,'');
+ }
+
+ return result;
}
}
diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts
index 8dd3d64f5b..db32114346 100644
--- a/packages/backend/src/core/RelayService.ts
+++ b/packages/backend/src/core/RelayService.ts
@@ -35,7 +35,7 @@ export class RelayService {
private createSystemUserService: CreateSystemUserService,
private apRendererService: ApRendererService,
) {
- this.relaysCache = new MemorySingleCache(1000 * 60 * 10);
+ this.relaysCache = new MemorySingleCache(1000 * 60 * 10); // 10m
}
@bindThis
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 64a81bbc15..7984dc5627 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -131,10 +131,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
private moderationLogService: ModerationLogService,
private fanoutTimelineService: FanoutTimelineService,
) {
- //this.onMessage = this.onMessage.bind(this);
-
- this.rolesCache = new MemorySingleCache(1000 * 60 * 60 * 1);
- this.roleAssignmentByUserIdCache = new MemoryKVCache(1000 * 60 * 60 * 1);
+ this.rolesCache = new MemorySingleCache(1000 * 60 * 60); // 1h
+ this.roleAssignmentByUserIdCache = new MemoryKVCache(1000 * 60 * 5); // 5m
this.redisForSub.on('message', this.onMessage);
}
diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts
index 51ac99179a..92d61cd103 100644
--- a/packages/backend/src/core/UserKeypairService.ts
+++ b/packages/backend/src/core/UserKeypairService.ts
@@ -25,7 +25,7 @@ export class UserKeypairService implements OnApplicationShutdown {
) {
this.cache = new RedisKVCache(this.redisClient, 'userKeypair', {
lifetime: 1000 * 60 * 60 * 24, // 24h
- memoryCacheLifetime: Infinity,
+ memoryCacheLifetime: 1000 * 60 * 60, // 1h
fetcher: (key) => this.userKeypairsRepository.findOneByOrFail({ userId: key }),
toRedisConverter: (value) => JSON.stringify(value),
fromRedisConverter: (value) => JSON.parse(value),
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index 44680a2ed5..062af39732 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -54,8 +54,8 @@ export class ApDbResolverService implements OnApplicationShutdown {
private cacheService: CacheService,
private apPersonService: ApPersonService,
) {
- this.publicKeyCache = new MemoryKVCache(Infinity);
- this.publicKeyByUserIdCache = new MemoryKVCache(Infinity);
+ this.publicKeyCache = new MemoryKVCache(1000 * 60 * 60 * 12); // 12h
+ this.publicKeyByUserIdCache = new MemoryKVCache(1000 * 60 * 60 * 12); // 12h
}
@bindThis
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 7e9a762c8d..55d1054de9 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -792,6 +792,13 @@ export class ApRendererService {
@bindThis
public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise {
+ // Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
+ // When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
+ // This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
+ if (!this.config.attachLdSignatureForRelays) {
+ return activity;
+ }
+
const keypair = await this.userKeypairService.getUserKeypair(user.id);
const jsonLd = this.jsonLdService.use();
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index bba64a06ef..d968069ca3 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -7,23 +7,23 @@ import * as Redis from 'ioredis';
import { bindThis } from '@/decorators.js';
export class RedisKVCache {
- private redisClient: Redis.Redis;
- private name: string;
- private lifetime: number;
- private memoryCache: MemoryKVCache;
- private fetcher: (key: string) => Promise;
- private toRedisConverter: (value: T) => string;
- private fromRedisConverter: (value: string) => T | undefined;
+ private readonly lifetime: number;
+ private readonly memoryCache: MemoryKVCache;
+ private readonly fetcher: (key: string) => Promise;
+ private readonly toRedisConverter: (value: T) => string;
+ private readonly fromRedisConverter: (value: string) => T | undefined;
- constructor(redisClient: RedisKVCache['redisClient'], name: RedisKVCache['name'], opts: {
- lifetime: RedisKVCache['lifetime'];
- memoryCacheLifetime: number;
- fetcher: RedisKVCache['fetcher'];
- toRedisConverter: RedisKVCache['toRedisConverter'];
- fromRedisConverter: RedisKVCache['fromRedisConverter'];
- }) {
- this.redisClient = redisClient;
- this.name = name;
+ constructor(
+ private redisClient: Redis.Redis,
+ private name: string,
+ opts: {
+ lifetime: RedisKVCache['lifetime'];
+ memoryCacheLifetime: number;
+ fetcher: RedisKVCache['fetcher'];
+ toRedisConverter: RedisKVCache['toRedisConverter'];
+ fromRedisConverter: RedisKVCache['fromRedisConverter'];
+ },
+ ) {
this.lifetime = opts.lifetime;
this.memoryCache = new MemoryKVCache(opts.memoryCacheLifetime);
this.fetcher = opts.fetcher;
@@ -55,7 +55,13 @@ export class RedisKVCache {
const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
if (cached == null) return undefined;
- return this.fromRedisConverter(cached);
+
+ const value = this.fromRedisConverter(cached);
+ if (value !== undefined) {
+ this.memoryCache.set(key, value);
+ }
+
+ return value;
}
@bindThis
@@ -77,14 +83,14 @@ export class RedisKVCache {
// Cache MISS
const value = await this.fetcher(key);
- this.set(key, value);
+ await this.set(key, value);
return value;
}
@bindThis
public async refresh(key: string) {
const value = await this.fetcher(key);
- this.set(key, value);
+ await this.set(key, value);
// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
}
@@ -101,23 +107,23 @@ export class RedisKVCache {
}
export class RedisSingleCache {
- private redisClient: Redis.Redis;
- private name: string;
- private lifetime: number;
- private memoryCache: MemorySingleCache;
- private fetcher: () => Promise;
- private toRedisConverter: (value: T) => string;
- private fromRedisConverter: (value: string) => T | undefined;
+ private readonly lifetime: number;
+ private readonly memoryCache: MemorySingleCache;
+ private readonly fetcher: () => Promise;
+ private readonly toRedisConverter: (value: T) => string;
+ private readonly fromRedisConverter: (value: string) => T | undefined;
- constructor(redisClient: RedisSingleCache['redisClient'], name: RedisSingleCache['name'], opts: {
- lifetime: RedisSingleCache['lifetime'];
- memoryCacheLifetime: number;
- fetcher: RedisSingleCache['fetcher'];
- toRedisConverter: RedisSingleCache['toRedisConverter'];
- fromRedisConverter: RedisSingleCache['fromRedisConverter'];
- }) {
- this.redisClient = redisClient;
- this.name = name;
+ constructor(
+ private redisClient: Redis.Redis,
+ private name: string,
+ opts: {
+ lifetime: number;
+ memoryCacheLifetime: number;
+ fetcher: RedisSingleCache['fetcher'];
+ toRedisConverter: RedisSingleCache['toRedisConverter'];
+ fromRedisConverter: RedisSingleCache['fromRedisConverter'];
+ },
+ ) {
this.lifetime = opts.lifetime;
this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime);
this.fetcher = opts.fetcher;
@@ -149,7 +155,13 @@ export class RedisSingleCache {
const cached = await this.redisClient.get(`singlecache:${this.name}`);
if (cached == null) return undefined;
- return this.fromRedisConverter(cached);
+
+ const value = this.fromRedisConverter(cached);
+ if (value !== undefined) {
+ this.memoryCache.set(value);
+ }
+
+ return value;
}
@bindThis
@@ -171,14 +183,14 @@ export class RedisSingleCache {
// Cache MISS
const value = await this.fetcher();
- this.set(value);
+ await this.set(value);
return value;
}
@bindThis
public async refresh() {
const value = await this.fetcher();
- this.set(value);
+ await this.set(value);
// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
}
@@ -187,22 +199,12 @@ export class RedisSingleCache {
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
export class MemoryKVCache {
- /**
- * データを持つマップ
- * @deprecated これを直接操作するべきではない
- */
- public cache: Map;
- private lifetime: number;
- private gcIntervalHandle: NodeJS.Timeout;
+ private readonly cache = new Map();
+ private readonly gcIntervalHandle = setInterval(() => this.gc(), 1000 * 60 * 3); // 3m
- constructor(lifetime: MemoryKVCache['lifetime']) {
- this.cache = new Map();
- this.lifetime = lifetime;
-
- this.gcIntervalHandle = setInterval(() => {
- this.gc();
- }, 1000 * 60 * 3);
- }
+ constructor(
+ private readonly lifetime: number,
+ ) {}
@bindThis
/**
@@ -287,10 +289,14 @@ export class MemoryKVCache {
@bindThis
public gc(): void {
const now = Date.now();
+
for (const [key, { date }] of this.cache.entries()) {
- if ((now - date) > this.lifetime) {
- this.cache.delete(key);
- }
+ // The map is ordered from oldest to youngest.
+ // We can stop once we find an entry that's still active, because all following entries must *also* be active.
+ const age = now - date;
+ if (age < this.lifetime) break;
+
+ this.cache.delete(key);
}
}
@@ -298,16 +304,19 @@ export class MemoryKVCache {
public dispose(): void {
clearInterval(this.gcIntervalHandle);
}
+
+ public get entries() {
+ return this.cache.entries();
+ }
}
export class MemorySingleCache {
private cachedAt: number | null = null;
private value: T | undefined;
- private lifetime: number;
- constructor(lifetime: MemorySingleCache['lifetime']) {
- this.lifetime = lifetime;
- }
+ constructor(
+ private lifetime: number,
+ ) {}
@bindThis
public set(value: T): void {
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index d665945861..4076e9da90 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -45,7 +45,7 @@ export class DeliverProcessorService {
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
- this.suspendedHostsCache = new MemorySingleCache(1000 * 60 * 60);
+ this.suspendedHostsCache = new MemorySingleCache(1000 * 60 * 60); // 1h
}
@bindThis
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index 716bb0944b..bc8d3c0411 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -135,7 +135,7 @@ export class NodeinfoServerService {
return document;
};
- const cache = new MemorySingleCache>>(1000 * 60 * 10);
+ const cache = new MemorySingleCache>>(1000 * 60 * 10); // 10m
fastify.get(nodeinfo2_1path, async (request, reply) => {
const base = await cache.fetch(() => nodeinfo2(21));
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index ddef8db987..690ff2e022 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -37,7 +37,7 @@ export class AuthenticateService implements OnApplicationShutdown {
private cacheService: CacheService,
) {
- this.appCache = new MemoryKVCache(Infinity);
+ this.appCache = new MemoryKVCache(1000 * 60 * 60 * 24 * 7); // 1w
}
@bindThis
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index 96038d9c1e..ef804b5bfd 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -38,8 +38,8 @@ export class UrlPreviewService {
) {
this.logger = this.loggerService.getLogger('url-preview');
this.previewCache = new RedisKVCache(this.redisClient, 'summaly', {
- lifetime: 1000 * 86400,
- memoryCacheLifetime: 1000 * 10 * 60,
+ lifetime: 1000 * 60 * 60 * 24, // 1d
+ memoryCacheLifetime: 1000 * 60 * 10, // 10m
fetcher: (key: string) => { throw new Error('the UrlPreview cache should never fetch'); },
toRedisConverter: (value) => JSON.stringify(value),
fromRedisConverter: (value) => JSON.parse(value),