From ca32c231d0aaab9dacaa88a0b0564d626827e797 Mon Sep 17 00:00:00 2001 From: piuvas Date: Mon, 26 May 2025 22:44:29 -0300 Subject: initial link attributions work. --- packages/backend/src/server/api/endpoints/i/update.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index f35e395841..dad605f151 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -263,6 +263,9 @@ export const paramDef = { enum: userUnsignedFetchOptions, nullable: false, }, + attributionDomains: { type: 'array', items: { + type: 'string', + } }, }, } as const; @@ -373,6 +376,7 @@ export default class extends Endpoint { // eslint- } if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances; if (ps.notificationRecieveConfig !== undefined) profileUpdates.notificationRecieveConfig = ps.notificationRecieveConfig; + if (ps.attributionDomains !== undefined) updates.attributionDomains = ps.attributionDomains; if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked; if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable; if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus; @@ -663,7 +667,7 @@ export default class extends Endpoint { // eslint- // these two methods need to be kept in sync with // `ApRendererService.renderPerson` private userNeedsPublishing(oldUser: MiLocalUser, newUser: Partial): boolean { - const basicFields: (keyof MiUser)[] = ['avatarId', 'bannerId', 'backgroundId', 'isBot', 'username', 'name', 'isLocked', 'isExplorable', 'isCat', 'noindex', 'speakAsCat', 'movedToUri', 'alsoKnownAs', 'hideOnlineStatus', 'enableRss', 'requireSigninToViewContents', 'makeNotesFollowersOnlyBefore', 'makeNotesHiddenBefore']; + const basicFields: (keyof MiUser)[] = ['avatarId', 'bannerId', 'backgroundId', 'isBot', 'username', 'name', 'isLocked', 'isExplorable', 'isCat', 'noindex', 'speakAsCat', 'movedToUri', 'alsoKnownAs', 'hideOnlineStatus', 'enableRss', 'requireSigninToViewContents', 'makeNotesFollowersOnlyBefore', 'makeNotesHiddenBefore', 'attributionDomains']; for (const field of basicFields) { if ((field in newUser) && oldUser[field] !== newUser[field]) { return true; -- cgit v1.2.3-freya From 3bf0a737c839f0b7a7ea93e16472f02ebff596ee Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 6 Jun 2025 13:17:20 -0400 Subject: match attributionDomains entity to database schema --- .../backend/src/core/activitypub/models/ApPersonService.ts | 12 ++++++++++-- packages/backend/src/models/User.ts | 4 ++-- packages/backend/src/server/api/endpoints/i/update.ts | 12 +++++++++--- 3 files changed, 21 insertions(+), 7 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index dde5762f53..631e86c8a8 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -445,7 +445,11 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { makeNotesFollowersOnlyBefore: (person as any).makeNotesFollowersOnlyBefore ?? null, makeNotesHiddenBefore: (person as any).makeNotesHiddenBefore ?? null, emojis, - attributionDomains: (Array.isArray(person.attributionDomains) && person.attributionDomains.every(x => typeof x === 'string')) ? person.attributionDomains : [], + attributionDomains: Array.isArray(person.attributionDomains) + ? person.attributionDomains + .filter((a: unknown) => typeof(a) === 'string' && a.length > 0 && a.length <= 128) + .slice(0, 32) + : [], })) as MiRemoteUser; let _description: string | null = null; @@ -629,7 +633,11 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { // We use "!== false" to handle incorrect types, missing / null values, and "default to true" logic. hideOnlineStatus: person.hideOnlineStatus !== false, isExplorable: person.discoverable !== false, - attributionDomains: (Array.isArray(person.attributionDomains) && person.attributionDomains.every(x => typeof x === 'string')) ? person.attributionDomains : [], + attributionDomains: Array.isArray(person.attributionDomains) + ? person.attributionDomains + .filter((a: unknown) => typeof(a) === 'string' && a.length > 0 && a.length <= 128) + .slice(0, 32) + : [], ...(await this.resolveAvatarAndBanner(exist, person.icon, person.image, person.backgroundUrl).catch(() => ({}))), } as Partial & Pick; diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 2f13400944..f40bb41a22 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -390,9 +390,9 @@ export class MiUser { }) public allowUnsignedFetch: UserUnsignedFetchOption; - @Column('varchar', { + @Column('text', { name: 'attributionDomains', - length: 128, array: true, default: '{}', + array: true, default: '{}', }) public attributionDomains: string[]; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index dad605f151..5a160d632c 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -263,9 +263,15 @@ export const paramDef = { enum: userUnsignedFetchOptions, nullable: false, }, - attributionDomains: { type: 'array', items: { - type: 'string', - } }, + attributionDomains: { + type: 'array', + items: { + type: 'string', + minLength: 1, + maxLength: 128, + }, + maxLength: 32, + }, }, } as const; -- cgit v1.2.3-freya From ce08bd1b425f14a1ca80287ca4cf43deeb32ea76 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 16 Mar 2025 12:37:46 -0400 Subject: reduce, clarify, and normalize more error messages --- packages/backend/src/boot/entry.ts | 16 +++++++++---- packages/backend/src/boot/master.ts | 2 +- packages/backend/src/core/DriveService.ts | 9 ++++---- .../src/core/FetchInstanceMetadataService.ts | 3 ++- packages/backend/src/core/NoteCreateService.ts | 2 -- packages/backend/src/core/WebAuthnService.ts | 10 ++++++-- packages/backend/src/core/WebfingerService.ts | 3 ++- .../backend/src/core/activitypub/ApInboxService.ts | 15 ++++++------ .../src/core/activitypub/models/ApNoteService.ts | 22 ++++++++---------- .../ExportAccountDataProcessorService.ts | 27 +++++++++++----------- .../processors/ExportAntennasProcessorService.ts | 2 +- .../processors/ExportBlockingProcessorService.ts | 2 +- .../ExportCustomEmojisProcessorService.ts | 4 ++-- .../processors/ExportFavoritesProcessorService.ts | 2 +- .../processors/ExportFollowingProcessorService.ts | 2 +- .../processors/ExportMutingProcessorService.ts | 2 +- .../processors/ExportUserListsProcessorService.ts | 2 +- .../processors/ImportAntennasProcessorService.ts | 4 ++-- .../processors/ImportBlockingProcessorService.ts | 2 +- .../ImportCustomEmojisProcessorService.ts | 9 ++++---- .../processors/ImportFollowingProcessorService.ts | 2 +- .../processors/ImportMutingProcessorService.ts | 3 ++- .../processors/ImportNotesProcessorService.ts | 26 ++++++++------------- .../processors/ImportUserListsProcessorService.ts | 3 ++- .../processors/ScheduleNotePostProcessorService.ts | 6 +++-- .../SystemWebhookDeliverProcessorService.ts | 3 ++- packages/backend/src/server/FileServerService.ts | 3 ++- packages/backend/src/server/ServerService.ts | 3 ++- packages/backend/src/server/api/ApiCallService.ts | 5 ++-- .../src/server/api/SigninWithPasskeyApiService.ts | 2 +- .../src/server/api/endpoints/drive/files/create.ts | 5 +++- .../backend/src/server/api/endpoints/i/move.ts | 4 ++-- .../backend/src/server/api/endpoints/i/update.ts | 3 ++- .../backend/src/server/api/endpoints/users/show.ts | 3 ++- 34 files changed, 114 insertions(+), 97 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts index 735a0f4666..e52d77ab9b 100644 --- a/packages/backend/src/boot/entry.ts +++ b/packages/backend/src/boot/entry.ts @@ -9,6 +9,7 @@ import cluster from 'node:cluster'; import { EventEmitter } from 'node:events'; +import { inspect } from 'node:util'; import chalk from 'chalk'; import Xev from 'xev'; import Logger from '@/logger.js'; @@ -53,15 +54,22 @@ async function main() { // Display detail of unhandled promise rejection if (!envOption.quiet) { - process.on('unhandledRejection', console.dir); + process.on('unhandledRejection', e => { + try { + logger.error('Unhandled rejection:', inspect(e)); + } catch { + console.error('Unhandled rejection:', inspect(e)); + } + }); } // Display detail of uncaught exception process.on('uncaughtException', err => { try { - logger.error(err); - console.trace(err); - } catch { } + logger.error('Uncaught exception:', err); + } catch { + console.error('Uncaught exception:', err); + } }); // Dying away... diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 538c529106..301db03e0e 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -172,7 +172,7 @@ function loadConfigBoot(): Config { config = loadConfig(); } catch (exception) { if (typeof exception === 'string') { - configLogger.error(exception); + configLogger.error('Exception loading config:', exception); process.exit(1); } else if ((exception as any).code === 'ENOENT') { configLogger.error('Configuration file not found', null, true); diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 73125f36d7..e1e74d0868 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -46,6 +46,7 @@ import { ModerationLogService } from '@/core/ModerationLogService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { BunnyService } from '@/core/BunnyService.js'; import { LoggerService } from './LoggerService.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; type AddFileArgs = { /** User who wish to add file */ @@ -311,7 +312,7 @@ export class DriveService { thumbnail, }; } catch (err) { - this.registerLogger.warn(`GenerateVideoThumbnail failed: ${err}`); + this.registerLogger.warn(`GenerateVideoThumbnail failed: ${renderInlineError(err)}`); return { webpublic: null, thumbnail: null, @@ -344,7 +345,7 @@ export class DriveService { metadata.height && metadata.height <= 2048 ); } catch (err) { - this.registerLogger.warn(`sharp failed: ${err}`); + this.registerLogger.warn(`sharp failed: ${renderInlineError(err)}`); return { webpublic: null, thumbnail: null, @@ -651,7 +652,7 @@ export class DriveService { userId: user ? user.id : IsNull(), }) as MiDriveFile; } else { - this.registerLogger.error(err as Error); + this.registerLogger.error('Error in drive register', err as Error); throw err; } } @@ -895,7 +896,7 @@ export class DriveService { this.downloaderLogger.succ(`Got: ${driveFile.id}`); return driveFile!; } catch (err) { - this.downloaderLogger.error(`Failed to create drive file: ${err}`, { + this.downloaderLogger.error(`Failed to create drive file: ${renderInlineError(err)}`, { url: url, e: err, }); diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 980f1fcacf..9f745f9459 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -15,6 +15,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import type { CheerioAPI } from 'cheerio'; type NodeInfo = { @@ -130,7 +131,7 @@ export class FetchInstanceMetadataService { this.logger.succ(`Successfuly updated metadata of ${instance.host}`); } catch (e) { - this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`); + this.logger.error(`Failed to update metadata of ${instance.host}: ${renderInlineError(e)}`); } finally { await this.unlock(host); } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 097d657ba3..3703926781 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -549,8 +549,6 @@ export class NoteCreateService implements OnApplicationShutdown { throw err; } - console.error(e); - throw e; } } diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index 372e1e2ab7..1b75811fff 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -17,6 +17,8 @@ import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; import { MiUser } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import Logger from '@/logger.js'; import type { AuthenticationResponseJSON, AuthenticatorTransportFuture, @@ -28,6 +30,8 @@ import type { @Injectable() export class WebAuthnService { + private readonly logger: Logger; + constructor( @Inject(DI.config) private config: Config, @@ -40,7 +44,9 @@ export class WebAuthnService { @Inject(DI.userSecurityKeysRepository) private userSecurityKeysRepository: UserSecurityKeysRepository, + loggerService: LoggerService, ) { + this.logger = loggerService.getLogger('web-authn'); } @bindThis @@ -114,7 +120,7 @@ export class WebAuthnService { requireUserVerification: true, }); } catch (error) { - console.error(error); + this.logger.error(error as Error, 'Error authenticating webauthn'); throw new IdentifiableError('5c1446f8-8ca7-4d31-9f39-656afe9c5d87', 'verification failed'); } @@ -301,7 +307,7 @@ export class WebAuthnService { requireUserVerification: true, }); } catch (error) { - console.error(error); + this.logger.error(error as Error, 'Error authenticating webauthn'); throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', 'verification failed'); } diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts index f57e7a2c1f..664963f3a3 100644 --- a/packages/backend/src/core/WebfingerService.ts +++ b/packages/backend/src/core/WebfingerService.ts @@ -9,6 +9,7 @@ import { XMLParser } from 'fast-xml-parser'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; import type Logger from '@/logger.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { RemoteLoggerService } from './RemoteLoggerService.js'; export type ILink = { @@ -109,7 +110,7 @@ export class WebfingerService { const template = (hostMeta['XRD']['Link'] as Array).filter(p => p['@_rel'] === 'lrdd')[0]['@_template']; return template.indexOf('{uri}') < 0 ? null : template; } catch (err) { - this.logger.error(`error while request host-meta for ${url}: ${err}`); + this.logger.error(`error while request host-meta for ${url}: ${renderInlineError(err)}`); return null; } } diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 0ffe0eea92..f4208b6e93 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -32,6 +32,7 @@ import { AbuseReportService } from '@/core/AbuseReportService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { fromTuple } from '@/misc/from-tuple.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import FederationChart from '@/core/chart/charts/federation.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; @@ -127,7 +128,7 @@ export class ApInboxService { results.push([id, result]); } catch (err) { if (err instanceof Error || typeof err === 'string') { - this.logger.error(err); + this.logger.error(`Unhandled error in activity ${getNullableApId(item || 'undefined')}:`, err); } else { throw err; } @@ -253,7 +254,7 @@ export class ApInboxService { resolver ??= this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(err => { - this.logger.error(`Resolution failed: ${err}`); + this.logger.error(`Resolution failed: ${renderInlineError(err)}`); throw err; }); @@ -326,7 +327,7 @@ export class ApInboxService { if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.'; const target = await resolver.secureResolve(activityObject, uri).catch(e => { - this.logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${renderInlineError(e)}`); throw e; }); @@ -499,7 +500,7 @@ export class ApInboxService { resolver ??= this.apResolverService.createResolver(); const object = await resolver.resolve(activityObject).catch(e => { - this.logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${renderInlineError(e)}`); throw e; }); @@ -668,7 +669,7 @@ export class ApInboxService { resolver ??= this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${renderInlineError(e)}`); throw e; }); @@ -740,7 +741,7 @@ export class ApInboxService { resolver ??= this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${renderInlineError(e)}`); throw e; }); @@ -872,7 +873,7 @@ export class ApInboxService { resolver ??= this.apResolverService.createResolver(); const object = await resolver.resolve(activity.object).catch(e => { - this.logger.error(`Resolution failed: ${e}`); + this.logger.error(`Resolution failed: ${renderInlineError(e)}`); throw e; }); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 5b66031bee..5b3def4bc3 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -26,6 +26,7 @@ import { bindThis } from '@/decorators.js'; import { checkHttps } from '@/misc/check-https.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { isRetryableError } from '@/misc/is-retryable-error.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { getOneApId, getApId, validPost, isEmoji, getApType, isApObject, isDocument, IApDocument } from '../type.js'; import { ApLoggerService } from '../ApLoggerService.js'; import { ApMfmService } from '../ApMfmService.js'; @@ -161,7 +162,7 @@ export class ApNoteService { const entryUri = getApId(value); const err = this.validateNote(object, entryUri, actor); if (err) { - this.logger.error(err.message, { + this.logger.error(`Error creating note: ${renderInlineError(err)}`, { resolver: { history: resolver.getHistory() }, value, object, @@ -269,14 +270,14 @@ export class ApNoteService { ? await this.resolveNote(note.inReplyTo, { resolver }) .then(x => { if (x == null) { - this.logger.warn('Specified inReplyTo, but not found'); + this.logger.warn(`Specified inReplyTo "${note.inReplyTo}", but not found`); throw new Error(`could not fetch inReplyTo ${note.inReplyTo} for note ${entryUri}`); } return x; }) .catch(async err => { - this.logger.warn(`error ${err.statusCode ?? err} fetching inReplyTo ${note.inReplyTo} for note ${entryUri}`); + this.logger.warn(`error ${renderInlineError(err)} fetching inReplyTo ${note.inReplyTo} for note ${entryUri}`); throw err; }) : null; @@ -379,7 +380,7 @@ export class ApNoteService { const entryUri = getApId(value); const err = this.validateNote(object, entryUri, actor, user); if (err) { - this.logger.error(err.message, { + this.logger.error(`Error updating note: ${renderInlineError(err)}`, { resolver: { history: resolver.getHistory() }, value, object, @@ -473,14 +474,14 @@ export class ApNoteService { ? await this.resolveNote(note.inReplyTo, { resolver }) .then(x => { if (x == null) { - this.logger.warn('Specified inReplyTo, but not found'); + this.logger.warn(`Specified inReplyTo "${note.inReplyTo}", but not found`); throw new Error(`could not fetch inReplyTo ${note.inReplyTo} for note ${entryUri}`); } return x; }) .catch(async err => { - this.logger.warn(`error ${err.statusCode ?? err} fetching inReplyTo ${note.inReplyTo} for note ${entryUri}`); + this.logger.warn(`error ${renderInlineError(err)} fetching inReplyTo ${note.inReplyTo} for note ${entryUri}`); throw err; }) : null; @@ -685,18 +686,13 @@ export class ApNoteService { const quote = await this.resolveNote(uri, { resolver }); if (quote == null) { - this.logger.warn(`Failed to resolve quote "${uri}" for note "${entryUri}": request error`); + this.logger.warn(`Failed to resolve quote "${uri}" for note "${entryUri}": fetch failed`); return false; } return quote; } catch (e) { - if (e instanceof Error) { - this.logger.warn(`Failed to resolve quote "${uri}" for note "${entryUri}":`, e); - } else { - this.logger.warn(`Failed to resolve quote "${uri}" for note "${entryUri}": ${e}`); - } - + this.logger.warn(`Failed to resolve quote "${uri}" for note "${entryUri}": ${renderInlineError(e)}`); return isRetryableError(e); } }; diff --git a/packages/backend/src/queue/processors/ExportAccountDataProcessorService.ts b/packages/backend/src/queue/processors/ExportAccountDataProcessorService.ts index 33a2362c4a..9caf5409dc 100644 --- a/packages/backend/src/queue/processors/ExportAccountDataProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAccountDataProcessorService.ts @@ -22,6 +22,7 @@ import { Packed } from '@/misc/json-schema.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { EmailService } from '@/core/EmailService.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -113,7 +114,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { userStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing user:', err); rej(err); } else { res(); @@ -145,7 +146,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { profileStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing profile:', err); rej(err); } else { res(); @@ -179,7 +180,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { ipStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing IPs:', err); rej(err); } else { res(); @@ -214,7 +215,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { notesStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing notes:', err); rej(err); } else { res(); @@ -275,7 +276,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { followingStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing following:', err); rej(err); } else { res(); @@ -345,7 +346,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { followerStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing followers:', err); rej(err); } else { res(); @@ -406,7 +407,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { filesStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing drive:', err); rej(err); } else { res(); @@ -432,7 +433,7 @@ export class ExportAccountDataProcessorService { await this.downloadService.downloadUrl(file.url, filePath); downloaded = true; } catch (e) { - this.logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error(`Error writing drive file ${file.id} (${file.name}): ${renderInlineError(e)}`); } if (!downloaded) { @@ -464,7 +465,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { mutingStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing mutings:', err); rej(err); } else { res(); @@ -527,7 +528,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { blockingStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing blockings:', err); rej(err); } else { res(); @@ -589,7 +590,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { favoriteStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing favorites:', err); rej(err); } else { res(); @@ -650,7 +651,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { antennaStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing antennas:', err); rej(err); } else { res(); @@ -708,7 +709,7 @@ export class ExportAccountDataProcessorService { return new Promise((res, rej) => { listStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error writing lists:', err); rej(err); } else { res(); diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index b3111865ad..3d3a32a597 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -53,7 +53,7 @@ export class ExportAntennasProcessorService { return new Promise((resolve, reject) => { stream.write(input, err => { if (err) { - this.logger.error(err); + this.logger.error('Error exporting antennas:', err); reject(); } else { resolve(); diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index ecc439db69..764bd062a7 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -87,7 +87,7 @@ export class ExportBlockingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.logger.error(err); + this.logger.error('Error exporting blocking:', err); rej(err); } else { res(); diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index 14d32e78b3..09ed0bfa56 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -66,7 +66,7 @@ export class ExportCustomEmojisProcessorService { return new Promise((res, rej) => { metaStream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error exporting custom emojis:', err); rej(err); } else { res(); @@ -101,7 +101,7 @@ export class ExportCustomEmojisProcessorService { await this.downloadService.downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; } catch (e) { // TODO: 何度か再試行 - this.logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error('Error exporting custom emojis:', e as Error); } if (!downloaded) { diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index b81feece01..32fcb5d4bc 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -64,7 +64,7 @@ export class ExportFavoritesProcessorService { return new Promise((res, rej) => { stream.write(text, err => { if (err) { - this.logger.error(err); + this.logger.error('Error exporting favorites:', err); rej(err); } else { res(); diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 903f962515..0b001dce75 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -98,7 +98,7 @@ export class ExportFollowingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.logger.error(err); + this.logger.error('Error exporting following:', err); rej(err); } else { res(); diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index f9867ade29..9b12269401 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -88,7 +88,7 @@ export class ExportMutingProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.logger.error(err); + this.logger.error('Error exporting mutings:', err); rej(err); } else { res(); diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index c483d79854..5483f1d47e 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -74,7 +74,7 @@ export class ExportUserListsProcessorService { await new Promise((res, rej) => { stream.write(content + '\n', err => { if (err) { - this.logger.error(err); + this.logger.error('Error exporting lists:', err); rej(err); } else { res(); diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 9c033b73e2..901bf5f70a 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -73,7 +73,7 @@ export class ImportAntennasProcessorService { for (const antenna of job.data.antenna) { if (antenna.keywords.length === 0 || antenna.keywords[0].every(x => x === '')) continue; if (!validate(antenna)) { - this.logger.warn('Validation Failed'); + this.logger.warn('Antenna validation failed'); continue; } const result = await this.antennasRepository.insertOne({ @@ -96,7 +96,7 @@ export class ImportAntennasProcessorService { this.globalEventService.publishInternalEvent('antennaCreated', result); } } catch (err: any) { - this.logger.error(err); + this.logger.error('Error importing antennas:', err); } } } diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index b78229c648..707bf093fc 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -97,7 +97,7 @@ export class ImportBlockingProcessorService { this.queueService.createBlockJob([{ from: { id: user.id }, to: { id: target.id }, silent: true }]); } catch (e) { - this.logger.warn(`Error: ${e}`); + this.logger.error('Error importing blockings:', e as Error); } } } diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index d08cadd378..4f54873924 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -16,6 +16,7 @@ import { DriveService } from '@/core/DriveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { bindThis } from '@/decorators.js'; import type { Config } from '@/config.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; @@ -65,7 +66,7 @@ export class ImportCustomEmojisProcessorService { await this.downloadService.downloadUrl(file.url, destPath, { operationTimeout: this.config.import?.downloadTimeout, maxSize: this.config.import?.maxFileSize }); } catch (e) { // TODO: 何度か再試行 if (e instanceof Error || typeof e === 'string') { - this.logger.error(e); + this.logger.error('Error importing custom emojis:', e as Error); } throw e; } @@ -117,7 +118,7 @@ export class ImportCustomEmojisProcessorService { }); } catch (e) { if (e instanceof Error || typeof e === 'string') { - this.logger.error(`couldn't import ${emojiPath} for ${emojiInfo.name}: ${e}`); + this.logger.error(`couldn't import ${emojiPath} for ${emojiInfo.name}: ${renderInlineError(e)}`); } continue; } @@ -127,9 +128,7 @@ export class ImportCustomEmojisProcessorService { this.logger.succ('Imported'); } catch (e) { - if (e instanceof Error || typeof e === 'string') { - this.logger.error(e); - } + this.logger.error('Error importing custom emojis:', e as Error); cleanup(); throw e; } diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 70c9f3a096..647faa142b 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -97,7 +97,7 @@ export class ImportFollowingProcessorService { this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true, withReplies: job.data.withReplies }]); } catch (e) { - this.logger.warn(`Error: ${e}`); + this.logger.error('Error importing followings:', e as Error); } } } diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index ec9d2b6c4c..8b90f7663b 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -14,6 +14,7 @@ import { DownloadService } from '@/core/DownloadService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; @@ -92,7 +93,7 @@ export class ImportMutingProcessorService { await this.userMutingService.mute(user, target); } catch (e) { - this.logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${renderInlineError(e)}`); } } diff --git a/packages/backend/src/queue/processors/ImportNotesProcessorService.ts b/packages/backend/src/queue/processors/ImportNotesProcessorService.ts index 5e660e8081..c2b8884f94 100644 --- a/packages/backend/src/queue/processors/ImportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportNotesProcessorService.ts @@ -192,9 +192,7 @@ export class ImportNotesProcessorService { await fsp.writeFile(destPath, '', 'binary'); await this.downloadUrl(file.url, destPath); } catch (e) { // TODO: 何度か再試行 - if (e instanceof Error || typeof e === 'string') { - this.logger.error(e); - } + this.logger.error('Error importing notes:', e as Error); throw e; } @@ -222,9 +220,7 @@ export class ImportNotesProcessorService { await fsp.writeFile(destPath, '', 'binary'); await this.downloadUrl(file.url, destPath); } catch (e) { // TODO: 何度か再試行 - if (e instanceof Error || typeof e === 'string') { - this.logger.error(e); - } + this.logger.error('Error importing notes:', e as Error); throw e; } @@ -255,9 +251,7 @@ export class ImportNotesProcessorService { await fsp.writeFile(destPath, '', 'binary'); await this.downloadUrl(file.url, destPath); } catch (e) { // TODO: 何度か再試行 - if (e instanceof Error || typeof e === 'string') { - this.logger.error(e); - } + this.logger.error('Error importing notes:', e as Error); throw e; } @@ -313,9 +307,7 @@ export class ImportNotesProcessorService { await fsp.writeFile(path, '', 'utf-8'); await this.downloadUrl(file.url, path); } catch (e) { // TODO: 何度か再試行 - if (e instanceof Error || typeof e === 'string') { - this.logger.error(e); - } + this.logger.error('Error importing notes:', e as Error); throw e; } @@ -365,7 +357,7 @@ export class ImportNotesProcessorService { try { await this.downloadUrl(file.url, filePath); } catch (e) { // TODO: 何度か再試行 - this.logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error('Error importing notes:', e as Error); } const driveFile = await this.driveService.addFile({ user: user, @@ -504,7 +496,7 @@ export class ImportNotesProcessorService { try { await this.downloadUrl(file.url, filePath); } catch (e) { // TODO: 何度か再試行 - this.logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error('Error importing notes:', e as Error); } const driveFile = await this.driveService.addFile({ user: user, @@ -628,7 +620,7 @@ export class ImportNotesProcessorService { try { await this.downloadUrl(videos[0].url, filePath); } catch (e) { // TODO: 何度か再試行 - this.logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error('Error importing notes:', e as Error); } const driveFile = await this.driveService.addFile({ user: user, @@ -653,7 +645,7 @@ export class ImportNotesProcessorService { try { await this.downloadUrl(file.media_url_https, filePath); } catch (e) { // TODO: 何度か再試行 - this.logger.error(e instanceof Error ? e : new Error(e as string)); + this.logger.error('Error importing notes:', e as Error); } const driveFile = await this.driveService.addFile({ @@ -673,7 +665,7 @@ export class ImportNotesProcessorService { const createdNote = await this.noteCreateService.import(user, { createdAt: date, reply: parentNote, text: text, files: files }); if (tweet.childNotes) this.queueService.createImportTweetsToDbJob(user, tweet.childNotes, createdNote.id); } catch (e) { - this.logger.warn(`Error: ${e}`); + this.logger.error('Error importing notes:', e as Error); } } diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index db9255b35d..d30ebfa187 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -15,6 +15,7 @@ import { UserListService } from '@/core/UserListService.js'; import { IdService } from '@/core/IdService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; @@ -102,7 +103,7 @@ export class ImportUserListsProcessorService { this.userListService.addMember(target, list!, user); } catch (e) { - this.logger.warn(`Error in line:${linenum} ${e}`); + this.logger.warn(`Error in line:${linenum} ${renderInlineError(e)}`); } } diff --git a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts index d823d98ef1..d96199c201 100644 --- a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts +++ b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts @@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js'; import { NotificationService } from '@/core/NotificationService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MiScheduleNoteType } from '@/models/NoteSchedule.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { ScheduleNotePostJobData } from '../types.js'; @@ -129,10 +130,11 @@ export class ScheduleNotePostProcessorService { channel, }).catch(async (err: IdentifiableError) => { this.notificationService.createNotification(me.id, 'scheduledNoteFailed', { - reason: err.message, + reason: renderInlineError(err), }); await this.noteScheduleRepository.remove(data); - throw this.logger.error(`Schedule Note Failed Reason: ${err.message}`); + this.logger.error(`Scheduled note failed:`, err); + throw err; }); await this.noteScheduleRepository.remove(data); this.notificationService.createNotification(me.id, 'scheduledNotePosted', { diff --git a/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts index f6bef52684..61042ff718 100644 --- a/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts @@ -12,6 +12,7 @@ import type Logger from '@/logger.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { StatusError } from '@/misc/status-error.js'; import { bindThis } from '@/decorators.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import { SystemWebhookDeliverJobData } from '../types.js'; @@ -63,7 +64,7 @@ export class SystemWebhookDeliverProcessorService { return 'Success'; } catch (res) { - this.logger.error(res as Error); + this.logger.error(`Failed to send webhook: ${renderInlineError(res)}`); this.systemWebhooksRepository.update({ id: job.data.webhookId }, { latestSentAt: new Date(), diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 4ef5539cff..2e3e8df7ea 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -32,6 +32,7 @@ import { getIpHash } from '@/misc/get-ip-hash.js'; import { AuthenticateService } from '@/server/api/AuthenticateService.js'; import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { Keyed, RateLimit, sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; const _filename = fileURLToPath(import.meta.url); @@ -120,7 +121,7 @@ export class FileServerService { @bindThis private async errorHandler(request: FastifyRequest<{ Params?: { [x: string]: any }; Querystring?: { [x: string]: any }; }>, reply: FastifyReply, err?: any) { - this.logger.error(`${err}`); + this.logger.error(`Unhandled error in file server: ${renderInlineError(err)}`); reply.header('Cache-Control', 'max-age=300'); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 2d20aa1222..77b4519570 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -21,6 +21,7 @@ import { genIdenticon } from '@/misc/gen-identicon.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { ActivityPubServerService } from './ActivityPubServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ApiServerService } from './api/ApiServerService.js'; @@ -277,7 +278,7 @@ export class ServerService implements OnApplicationShutdown { this.logger.error(`Port ${this.config.port} is already in use by another process.`); break; default: - this.logger.error(err); + this.logger.error(`Unhandled error in server: ${renderInlineError(err)}`); break; } diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 5c9e5717bb..ce0078cd93 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -20,6 +20,7 @@ import { RoleService } from '@/core/RoleService.js'; import type { Config } from '@/config.js'; import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { ApiError } from './error.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; @@ -100,7 +101,7 @@ export class ApiCallService implements OnApplicationShutdown { throw err; } else { const errId = randomUUID(); - this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, { + this.logger.error(`Internal error occurred in ${ep.name}: ${renderInlineError(err)}`, { ep: ep.name, ps: data, e: { @@ -112,7 +113,7 @@ export class ApiCallService implements OnApplicationShutdown { }); if (this.config.sentryForBackend) { - Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, { + Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${renderInlineError(err)}`, { level: 'error', user: { id: userId, diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts index f84f50523b..38886f8876 100644 --- a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -128,7 +128,7 @@ export class SigninWithPasskeyApiService { try { authorizedUserId = await this.webAuthnService.verifySignInWithPasskeyAuthentication(context, credential); } catch (err) { - this.logger.warn(`Passkey challenge Verify error! : ${err}`); + this.logger.warn('Passkey challenge verify error:', err as Error); const errorId = (err as IdentifiableError).id; return error(403, { id: errorId, diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 5373cc4dd8..939eadad9b 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -10,6 +10,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DriveService } from '@/core/DriveService.js'; import type { Config } from '@/config.js'; +import { ApiLoggerService } from '@/server/api/ApiLoggerService.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { ApiError } from '../../../error.js'; import { MiMeta } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; @@ -95,6 +97,7 @@ export default class extends Endpoint { // eslint- private driveFileEntityService: DriveFileEntityService, private driveService: DriveService, + private readonly apiLoggerService: ApiLoggerService, ) { super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => { // Get 'name' parameter @@ -130,7 +133,7 @@ export default class extends Endpoint { // eslint- return await this.driveFileEntityService.pack(driveFile, { self: true }); } catch (err) { if (err instanceof Error || typeof err === 'string') { - console.error('Error saving drive file:', err); + this.apiLoggerService.logger.error(`Error saving drive file: ${renderInlineError(err)}`); } if (err instanceof IdentifiableError) { if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate); diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index 7852b5a2e1..e2a14b61af 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -17,7 +17,7 @@ import { ApiLoggerService } from '@/server/api/ApiLoggerService.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; - +import { renderInlineError } from '@/misc/render-inline-error.js'; import * as Acct from '@/misc/acct.js'; import { DI } from '@/di-symbols.js'; import { MiMeta } from '@/models/_.js'; @@ -105,7 +105,7 @@ export default class extends Endpoint { // eslint- const { username, host } = Acct.parse(ps.moveToAccount); // retrieve the destination account let moveTo = await this.remoteUserResolveService.resolveUser(username, host).catch((e) => { - this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`); + this.apiLoggerService.logger.warn(`failed to resolve remote user: ${renderInlineError(e)}`); throw new ApiError(meta.errors.noSuchUser); }); const destination = await this.getterService.getUser(moveTo.id) as MiLocalUser | MiRemoteUser; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 5a160d632c..193f2e8313 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -34,6 +34,7 @@ import { verifyFieldLinks } from '@/misc/verify-field-link.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; import { notificationRecieveConfig } from '@/models/json-schema/user.js'; import { userUnsignedFetchOptions } from '@/const.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { ApiLoggerService } from '../../ApiLoggerService.js'; import { ApiError } from '../../error.js'; @@ -516,7 +517,7 @@ export default class extends Endpoint { // eslint- // Retrieve the old account const knownAs = await this.remoteUserResolveService.resolveUser(username, host).catch((e) => { - this.apiLoggerService.logger.warn(`failed to resolve dstination user: ${e}`); + this.apiLoggerService.logger.warn(`failed to resolve destination user: ${renderInlineError(e)}`); throw new ApiError(meta.errors.noSuchUser); }); if (knownAs.id === _user.id) throw new ApiError(meta.errors.forbiddenToSetYourself); diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 7b1c8adfb8..84eb661742 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -13,6 +13,7 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DI } from '@/di-symbols.js'; import PerUserPvChart from '@/core/chart/charts/per-user-pv.js'; import { RoleService } from '@/core/RoleService.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { ApiError } from '../../error.js'; import { ApiLoggerService } from '../../ApiLoggerService.js'; import type { FindOptionsWhere } from 'typeorm'; @@ -131,7 +132,7 @@ export default class extends Endpoint { // eslint- // Lookup user if (typeof ps.host === 'string' && typeof ps.username === 'string') { user = await this.remoteUserResolveService.resolveUser(ps.username, ps.host).catch(err => { - this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`); + this.apiLoggerService.logger.warn(`failed to resolve remote user: ${renderInlineError(err)}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); }); } else { -- cgit v1.2.3-freya From 2cba0ada3cdf6b7ee37fa5c0b54134160be657a9 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 22 May 2025 12:27:54 -0400 Subject: more use of identifiable errors, improvements to inner error rendering, and more heuristics for is-retryable-error --- packages/backend/src/core/AbuseReportService.ts | 5 +- packages/backend/src/core/CaptchaService.ts | 10 +-- packages/backend/src/core/NoteCreateService.ts | 10 +-- packages/backend/src/core/NoteEditService.ts | 6 +- .../backend/src/core/RemoteUserResolveService.ts | 5 +- packages/backend/src/core/WebAuthnService.ts | 6 +- .../core/activitypub/ApDeliverManagerService.ts | 5 +- .../backend/src/core/activitypub/JsonLdService.ts | 3 +- .../backend/src/core/activitypub/misc/validator.ts | 2 - .../src/core/activitypub/models/ApImageService.ts | 4 +- .../src/core/activitypub/models/ApNoteService.ts | 48 ++++++-------- .../src/core/activitypub/models/ApPersonService.ts | 49 +++++++------- packages/backend/src/misc/FileWriterStream.ts | 2 +- packages/backend/src/misc/fastify-reply-error.ts | 4 +- packages/backend/src/misc/id/aid.ts | 3 +- packages/backend/src/misc/id/aidx.ts | 3 +- packages/backend/src/misc/identifiable-error.ts | 4 +- packages/backend/src/misc/is-retryable-error.ts | 12 ++++ packages/backend/src/misc/render-inline-error.ts | 50 +++++++++++---- packages/backend/src/misc/status-error.ts | 4 +- .../queue/processors/DeliverProcessorService.ts | 23 +++---- .../src/queue/processors/InboxProcessorService.ts | 2 +- .../processors/ScheduleNotePostProcessorService.ts | 2 +- .../SystemWebhookDeliverProcessorService.ts | 9 +-- .../UserWebhookDeliverProcessorService.ts | 9 +-- .../backend/src/server/SkRateLimiterService.ts | 2 +- .../backend/src/server/api/SigninApiService.ts | 12 ++-- .../backend/src/server/api/SignupApiService.ts | 16 ++--- .../src/server/api/endpoints/admin/relays/add.ts | 7 +- .../server/api/endpoints/gallery/posts/create.ts | 2 +- .../server/api/endpoints/gallery/posts/update.ts | 2 +- .../src/server/api/endpoints/i/2fa/remove-key.ts | 2 +- .../backend/test/unit/misc/is-retryable-error.ts | 75 ++++++++++++++++++++++ 33 files changed, 241 insertions(+), 157 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/src/core/AbuseReportService.ts b/packages/backend/src/core/AbuseReportService.ts index 846d2c8ebd..bccb9f86f6 100644 --- a/packages/backend/src/core/AbuseReportService.ts +++ b/packages/backend/src/core/AbuseReportService.ts @@ -13,6 +13,7 @@ import { QueueService } from '@/core/QueueService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { SystemAccountService } from '@/core/SystemAccountService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdService } from './IdService.js'; @Injectable() @@ -125,11 +126,11 @@ export class AbuseReportService { const report = await this.abuseUserReportsRepository.findOneByOrFail({ id: reportId }); if (report.targetUserHost == null) { - throw new Error('The target user host is null.'); + throw new IdentifiableError('0b1ce202-b2c1-4ee4-8af4-2742a51b383d', 'The target user host is null.'); } if (report.forwarded) { - throw new Error('The report has already been forwarded.'); + throw new IdentifiableError('5c008bdf-f0e8-4154-9f34-804e114516d7', 'The report has already been forwarded.'); } await this.abuseUserReportsRepository.update(report.id, { diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 13200bf7b3..c526a80aeb 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -54,7 +54,7 @@ export class CaptchaError extends Error { public readonly cause?: unknown; constructor(code: CaptchaErrorCode, message: string, cause?: unknown) { - super(message); + super(message, cause ? { cause } : undefined); this.code = code; this.cause = cause; this.name = 'CaptchaError'; @@ -117,7 +117,7 @@ export class CaptchaService { } const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => { - throw new CaptchaError(captchaErrorCodes.requestFailed, `recaptcha-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `recaptcha-request-failed: ${err}`, err); }); if (result.success !== true) { @@ -133,7 +133,7 @@ export class CaptchaService { } const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => { - throw new CaptchaError(captchaErrorCodes.requestFailed, `hcaptcha-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `hcaptcha-request-failed: ${err}`, err); }); if (result.success !== true) { @@ -209,7 +209,7 @@ export class CaptchaService { } const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => { - throw new CaptchaError(captchaErrorCodes.requestFailed, `turnstile-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `turnstile-request-failed: ${err}`, err); }); if (result.success !== true) { @@ -386,7 +386,7 @@ export class CaptchaService { this.logger.info(err); const error = err instanceof CaptchaError ? err - : new CaptchaError(captchaErrorCodes.unknown, `unknown error: ${err}`); + : new CaptchaError(captchaErrorCodes.unknown, `unknown error: ${err}`, err); return { success: false, error, diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 3703926781..4dceb6e953 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -296,7 +296,7 @@ export class NoteCreateService implements OnApplicationShutdown { case 'followers': // 他人のfollowers noteはreject if (data.renote.userId !== user.id) { - throw new Error('Renote target is not public or home'); + throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home'); } // Renote対象がfollowersならfollowersにする @@ -304,7 +304,7 @@ export class NoteCreateService implements OnApplicationShutdown { break; case 'specified': // specified / direct noteはreject - throw new Error('Renote target is not public or home'); + throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home'); } } @@ -317,7 +317,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.renote.userId !== user.id) { const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id); if (blocked) { - throw new Error('blocked'); + throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked'); } } } @@ -489,10 +489,10 @@ export class NoteCreateService implements OnApplicationShutdown { // should really not happen, but better safe than sorry if (data.reply?.id === insert.id) { - throw new Error('A note can\'t reply to itself'); + throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', 'A note can\'t reply to itself'); } if (data.renote?.id === insert.id) { - throw new Error('A note can\'t renote itself'); + throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', 'A note can\'t renote itself'); } if (data.uri != null) insert.uri = data.uri; diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 58233b90ee..34af1c76dd 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -309,7 +309,7 @@ export class NoteEditService implements OnApplicationShutdown { if (this.isRenote(data)) { if (data.renote.id === oldnote.id) { - throw new UnrecoverableError(`edit failed for ${oldnote.id}: cannot renote itself`); + throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', `edit failed for ${oldnote.id}: cannot renote itself`); } switch (data.renote.visibility) { @@ -325,7 +325,7 @@ export class NoteEditService implements OnApplicationShutdown { case 'followers': // 他人のfollowers noteはreject if (data.renote.userId !== user.id) { - throw new Error('Renote target is not public or home'); + throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home'); } // Renote対象がfollowersならfollowersにする @@ -333,7 +333,7 @@ export class NoteEditService implements OnApplicationShutdown { break; case 'specified': // specified / direct noteはreject - throw new Error('Renote target is not public or home'); + throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home'); } } diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts index f4ee726765..4dbc9d6a36 100644 --- a/packages/backend/src/core/RemoteUserResolveService.ts +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -17,6 +17,7 @@ import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { bindThis } from '@/decorators.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; @Injectable() export class RemoteUserResolveService { @@ -119,8 +120,8 @@ export class RemoteUserResolveService { @bindThis private async resolveSelf(acctLower: string): Promise { const finger = await this.webfingerService.webfinger(acctLower).catch(err => { - this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`); - throw new Error(`Failed to WebFinger for ${acctLower}: ${ err.statusCode ?? err.message }`); + this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${renderInlineError(err)}`); + throw new Error(`Failed to WebFinger for ${acctLower}: error thrown`, { cause: err }); }); const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); if (!self) { diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index 1b75811fff..afd1d68ce4 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -121,7 +121,7 @@ export class WebAuthnService { }); } catch (error) { this.logger.error(error as Error, 'Error authenticating webauthn'); - throw new IdentifiableError('5c1446f8-8ca7-4d31-9f39-656afe9c5d87', 'verification failed'); + throw new IdentifiableError('5c1446f8-8ca7-4d31-9f39-656afe9c5d87', 'verification failed', true, error); } const { verified } = verification; @@ -227,7 +227,7 @@ export class WebAuthnService { requireUserVerification: true, }); } catch (error) { - throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', `verification failed: ${error}`); + throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', `verification failed`, true, error); } const { verified, authenticationInfo } = verification; @@ -308,7 +308,7 @@ export class WebAuthnService { }); } catch (error) { this.logger.error(error as Error, 'Error authenticating webauthn'); - throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', 'verification failed'); + throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', 'verification failed', true, error); } const { verified, authenticationInfo } = verification; diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index eaa592b9e0..746af41f55 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -57,7 +57,7 @@ class DeliverManager { ) { // 型で弾いてはいるが一応ローカルユーザーかチェック // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (actor.host != null) throw new Error('actor.host must be null'); + if (actor.host != null) throw new Error(`deliver failed for ${actor.id}: host is not null`); // パフォーマンス向上のためキューに突っ込むのはidのみに絞る this.actor = { @@ -124,12 +124,13 @@ class DeliverManager { select: { followerSharedInbox: true, followerInbox: true, + followerId: true, }, }); for (const following of followers) { const inbox = following.followerSharedInbox ?? following.followerInbox; - if (inbox === null) throw new UnrecoverableError(`inbox is null: following ${following.id}`); + if (inbox === null) throw new UnrecoverableError(`deliver failed for ${this.actor.id}: follower ${following.followerId} inbox is null`); inboxes.set(inbox, following.followerSharedInbox != null); } } diff --git a/packages/backend/src/core/activitypub/JsonLdService.ts b/packages/backend/src/core/activitypub/JsonLdService.ts index 9d1e2e06cc..1db5df6ad9 100644 --- a/packages/backend/src/core/activitypub/JsonLdService.ts +++ b/packages/backend/src/core/activitypub/JsonLdService.ts @@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common'; import { UnrecoverableError } from 'bullmq'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; +import { StatusError } from '@/misc/status-error.js'; import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js'; import { validateContentTypeSetAsJsonLD } from './misc/validator.js'; import type { JsonLdDocument } from 'jsonld'; @@ -149,7 +150,7 @@ class JsonLd { }, ).then(res => { if (!res.ok) { - throw new Error(`JSON-LD fetch failed with ${res.status} ${res.statusText}: ${url}`); + throw new StatusError(`failed to fetch JSON-LD from ${url}`, res.status, res.statusText); } else { return res.json(); } diff --git a/packages/backend/src/core/activitypub/misc/validator.ts b/packages/backend/src/core/activitypub/misc/validator.ts index e3761e3d14..5cd2ddf006 100644 --- a/packages/backend/src/core/activitypub/misc/validator.ts +++ b/packages/backend/src/core/activitypub/misc/validator.ts @@ -6,8 +6,6 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { Response } from 'node-fetch'; -// TODO throw identifiable or unrecoverable errors - export function validateContentTypeSetAsActivityPub(response: Response): void { const contentType = (response.headers.get('content-type') ?? '').toLowerCase(); diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 423044b985..7a16972ea4 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -18,7 +18,7 @@ import type { Config } from '@/config.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; -import { isDocument, type IObject } from '../type.js'; +import { getNullableApId, isDocument, type IObject } from '../type.js'; @Injectable() export class ApImageService { @@ -48,7 +48,7 @@ export class ApImageService { public async createImage(actor: MiRemoteUser, value: string | IObject): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { - throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', `actor has been suspended: ${actor.uri}`); + throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', `failed to create image ${getNullableApId(value)}: actor ${actor.id} has been suspended`); } const image = await this.apResolverService.createResolver().resolve(value); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index e9f885119f..57d4303982 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -175,11 +175,11 @@ export class ApNoteService { this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); if (note.id == null) { - throw new UnrecoverableError(`Refusing to create note without id: ${entryUri}`); + throw new UnrecoverableError(`failed to create note ${entryUri}: missing ID`); } if (!checkHttps(note.id)) { - throw new UnrecoverableError(`unexpected schema of note.id ${note.id} in ${entryUri}`); + throw new UnrecoverableError(`failed to create note ${entryUri}: unexpected schema`); } const url = this.apUtilityService.findBestObjectUrl(note); @@ -188,7 +188,7 @@ export class ApNoteService { // 投稿者をフェッチ if (note.attributedTo == null) { - throw new UnrecoverableError(`invalid note.attributedTo ${note.attributedTo} in ${entryUri}`); + throw new UnrecoverableError(`failed to create note: ${entryUri}: missing attributedTo`); } const uri = getOneApId(note.attributedTo); @@ -271,14 +271,14 @@ export class ApNoteService { .then(x => { if (x == null) { this.logger.warn(`Specified inReplyTo "${note.inReplyTo}", but not found`); - throw new Error(`could not fetch inReplyTo ${note.inReplyTo} for note ${entryUri}`); + throw new IdentifiableError('1ebf0a96-2769-4973-a6c2-3dcbad409dff', `failed to create note ${entryUri}: could not fetch inReplyTo ${note.inReplyTo}`, true); } return x; }) .catch(async err => { this.logger.warn(`error ${renderInlineError(err)} fetching inReplyTo ${note.inReplyTo} for note ${entryUri}`); - throw err; + throw new IdentifiableError('1ebf0a96-2769-4973-a6c2-3dcbad409dff', `failed to create note ${entryUri}: could not fetch inReplyTo ${note.inReplyTo}`, true, err); }) : null; @@ -349,7 +349,7 @@ export class ApNoteService { this.logger.info('The note is already inserted while creating itself, reading again'); const duplicate = await this.fetchNote(value); if (!duplicate) { - throw new Error(`The note creation failed with duplication error even when there is no duplication: ${entryUri}`); + throw new IdentifiableError('39c328e1-e829-458b-bfc9-65dcd513d1f8', `failed to create note ${entryUri}: the note creation failed with duplication error even when there is no duplication. This is likely a bug.`); } return duplicate; } @@ -363,45 +363,39 @@ export class ApNoteService { const noteUri = getApId(value); // URIがこのサーバーを指しているならスキップ - if (noteUri.startsWith(this.config.url + '/')) throw new UnrecoverableError(`uri points local: ${noteUri}`); + if (this.utilityService.isUriLocal(noteUri)) { + throw new UnrecoverableError(`failed to update note ${noteUri}: uri is local`); + } //#region このサーバーに既に登録されているか const updatedNote = await this.notesRepository.findOneBy({ uri: noteUri }); - if (updatedNote == null) throw new Error(`Note is not registered (no note): ${noteUri}`); + if (updatedNote == null) throw new UnrecoverableError(`failed to update note ${noteUri}: note does not exist`); const user = await this.usersRepository.findOneBy({ id: updatedNote.userId }) as MiRemoteUser | null; - if (user == null) throw new Error(`Note is not registered (no user): ${noteUri}`); + if (user == null) throw new UnrecoverableError(`failed to update note ${noteUri}: user does not exist`); - // eslint-disable-next-line no-param-reassign - if (resolver == null) resolver = this.apResolverService.createResolver(); + resolver ??= this.apResolverService.createResolver(); const object = await resolver.resolve(value); const entryUri = getApId(value); const err = this.validateNote(object, entryUri, actor, user); if (err) { - this.logger.error(`Error updating note: ${renderInlineError(err)}`, { - resolver: { history: resolver.getHistory() }, - value, - object, - }); + this.logger.error(`Failed to update note ${noteUri}: ${renderInlineError(err)}`); throw err; } // `validateNote` checks that the actor and user are one and the same - // eslint-disable-next-line no-param-reassign actor ??= user; const note = object as IPost; - this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - if (note.id == null) { - throw new UnrecoverableError(`Refusing to update note without id: ${noteUri}`); + throw new UnrecoverableError(`failed to update note ${entryUri}: missing ID`); } if (!checkHttps(note.id)) { - throw new UnrecoverableError(`unexpected schema of note.id ${note.id} in ${noteUri}`); + throw new UnrecoverableError(`failed to update note ${entryUri}: unexpected schema`); } const url = this.apUtilityService.findBestObjectUrl(note); @@ -475,14 +469,14 @@ export class ApNoteService { .then(x => { if (x == null) { this.logger.warn(`Specified inReplyTo "${note.inReplyTo}", but not found`); - throw new Error(`could not fetch inReplyTo ${note.inReplyTo} for note ${entryUri}`); + throw new IdentifiableError('1ebf0a96-2769-4973-a6c2-3dcbad409dff', `failed to update note ${entryUri}: could not fetch inReplyTo ${note.inReplyTo}`, true); } return x; }) .catch(async err => { this.logger.warn(`error ${renderInlineError(err)} fetching inReplyTo ${note.inReplyTo} for note ${entryUri}`); - throw err; + throw new IdentifiableError('1ebf0a96-2769-4973-a6c2-3dcbad409dff', `failed to update note ${entryUri}: could not fetch inReplyTo ${note.inReplyTo}`, true, err); }) : null; @@ -550,7 +544,7 @@ export class ApNoteService { this.logger.info('The note is already inserted while creating itself, reading again'); const duplicate = await this.fetchNote(value); if (!duplicate) { - throw new Error(`The note creation failed with duplication error even when there is no duplication: ${noteUri}`); + throw new IdentifiableError('39c328e1-e829-458b-bfc9-65dcd513d1f8', `failed to update note ${entryUri}: the note update failed with duplication error even when there is no duplication. This is likely a bug.`); } return duplicate; } @@ -567,8 +561,7 @@ export class ApNoteService { const uri = getApId(value); if (!this.utilityService.isFederationAllowedUri(uri)) { - // TODO convert to identifiable error - throw new StatusError(`blocked host: ${uri}`, 451, 'blocked host'); + throw new IdentifiableError('04620a7e-044e-45ce-b72c-10e1bdc22e69', `failed to resolve note ${uri}: host is blocked`); } //#region このサーバーに既に登録されていたらそれを返す @@ -578,8 +571,7 @@ export class ApNoteService { // Bail if local URI doesn't exist if (this.utilityService.isUriLocal(uri)) { - // TODO convert to identifiable error - throw new StatusError(`cannot resolve local note: ${uri}`, 400, 'cannot resolve local note'); + throw new IdentifiableError('cbac7358-23f2-4c70-833e-cffb4bf77913', `failed to resolve note ${uri}: URL is local and does not exist`); } const unlock = await this.appLockService.getApLock(uri); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 993b938f3a..b7aa036068 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -55,6 +55,7 @@ import type { ApLoggerService } from '../ApLoggerService.js'; import type { ApImageService } from './ApImageService.js'; import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; const nameLength = 128; const summaryLength = 2048; @@ -158,21 +159,21 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { const expectHost = this.utilityService.punyHostPSLDomain(uri); if (!isActor(x)) { - throw new UnrecoverableError(`invalid Actor type '${x.type}' in ${uri}`); + throw new UnrecoverableError(`invalid Actor ${uri}: unknown type '${x.type}'`); } if (!(typeof x.id === 'string' && x.id.length > 0)) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong id type`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong id type`); } if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong inbox type`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong inbox type`); } this.apUtilityService.assertApUrl(x.inbox); const inboxHost = this.utilityService.punyHostPSLDomain(x.inbox); if (inboxHost !== expectHost) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong inbox ${inboxHost}`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong inbox host ${inboxHost}`); } const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined); @@ -180,7 +181,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { const sharedInbox = getApId(sharedInboxObject); this.apUtilityService.assertApUrl(sharedInbox); if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && this.utilityService.punyHostPSLDomain(sharedInbox) === expectHost)) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong shared inbox ${sharedInbox}`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong shared inbox ${sharedInbox}`); } } @@ -191,7 +192,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { if (typeof collectionUri === 'string' && collectionUri.length > 0) { this.apUtilityService.assertApUrl(collectionUri); if (this.utilityService.punyHostPSLDomain(collectionUri) !== expectHost) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong ${collection} ${collectionUri}`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong ${collection} host ${collectionUri}`); } } else if (collectionUri != null) { throw new UnrecoverableError(`invalid Actor ${uri}: wrong ${collection} type`); @@ -200,7 +201,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { } if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong username`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong username`); } // These fields are only informational, and some AP software allows these @@ -208,7 +209,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { // we can at least see these users and their activities. if (x.name) { if (!(typeof x.name === 'string' && x.name.length > 0)) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong name`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong name`); } x.name = truncate(x.name, nameLength); } else if (x.name === '') { @@ -217,24 +218,24 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { } if (x.summary) { if (!(typeof x.summary === 'string' && x.summary.length > 0)) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong summary`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong summary`); } x.summary = truncate(x.summary, summaryLength); } const idHost = this.utilityService.punyHostPSLDomain(x.id); if (idHost !== expectHost) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong id ${x.id}`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong id ${x.id}`); } if (x.publicKey) { if (typeof x.publicKey.id !== 'string') { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong publicKey.id type`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong publicKey.id type`); } const publicKeyIdHost = this.utilityService.punyHostPSLDomain(x.publicKey.id); if (publicKeyIdHost !== expectHost) { - throw new UnrecoverableError(`invalid Actor ${uri} - wrong publicKey.id ${x.publicKey.id}`); + throw new UnrecoverableError(`invalid Actor ${uri}: wrong publicKey.id ${x.publicKey.id}`); } } @@ -272,8 +273,6 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { } private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any, bgimg: any): Promise>> { - if (user == null) throw new Error('failed to create user: user is null'); - const [avatar, banner, background] = await Promise.all([icon, image, bgimg].map(img => { // icon and image may be arrays // see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-icon @@ -326,12 +325,11 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { */ @bindThis public async createPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new UnrecoverableError(`uri is not string: ${uri}`); + if (typeof uri !== 'string') throw new UnrecoverableError(`failed to create user ${uri}: input is not string`); const host = this.utilityService.punyHost(uri); if (host === this.utilityService.toPuny(this.config.host)) { - // TODO convert to unrecoverable error - throw new StatusError(`cannot resolve local user: ${uri}`, 400, 'cannot resolve local user'); + throw new UnrecoverableError(`failed to create user ${uri}: URI is local`); } return await this._createPerson(uri, resolver); @@ -341,8 +339,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { const uri = getApId(value); const host = this.utilityService.punyHost(uri); - // eslint-disable-next-line no-param-reassign - if (resolver == null) resolver = this.apResolverService.createResolver(); + resolver ??= this.apResolverService.createResolver(); const object = await resolver.resolve(value); const person = this.validateActor(object, uri); @@ -375,7 +372,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); if (person.id == null) { - throw new UnrecoverableError(`Refusing to create person without id: ${uri}`); + throw new UnrecoverableError(`failed to create user ${uri}: missing ID`); } const url = this.apUtilityService.findBestObjectUrl(person); @@ -568,7 +565,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { */ @bindThis public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject, movePreventUris: string[] = []): Promise { - if (typeof uri !== 'string') throw new UnrecoverableError('uri is not string'); + if (typeof uri !== 'string') throw new UnrecoverableError(`failed to update user ${uri}: input is not string`); // URIがこのサーバーを指しているならスキップ if (this.utilityService.isUriLocal(uri)) return; @@ -624,7 +621,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); if (person.id == null) { - throw new UnrecoverableError(`Refusing to update person without id: ${uri}`); + throw new UnrecoverableError(`failed to update user ${uri}: missing ID`); } const url = this.apUtilityService.findBestObjectUrl(person); @@ -793,8 +790,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { const uri = getApId(value); if (!this.utilityService.isFederationAllowedUri(uri)) { - // TODO convert to identifiable error - throw new StatusError(`blocked host: ${uri}`, 451, 'blocked host'); + throw new IdentifiableError('590719b3-f51f-48a9-8e7d-6f559ad00e5d', `failed to resolve person ${uri}: host is blocked`); } //#region このサーバーに既に登録されていたらそれを返す @@ -804,8 +800,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { // Bail if local URI doesn't exist if (this.utilityService.isUriLocal(uri)) { - // TODO convert to identifiable error - throw new StatusError(`cannot resolve local person: ${uri}`, 400, 'cannot resolve local person'); + throw new IdentifiableError('efb573fd-6b9e-4912-9348-a02f5603df4f', `failed to resolve person ${uri}: URL is local and does not exist`); } const unlock = await this.appLockService.getApLock(uri); @@ -859,7 +854,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown { }) : null; if (!collection) return; - if (!isCollectionOrOrderedCollection(collection)) throw new UnrecoverableError(`featured ${user.featured} is not Collection or OrderedCollection in ${user.uri}`); + if (!isCollectionOrOrderedCollection(collection)) throw new UnrecoverableError(`failed to update user ${user.uri}: featured ${user.featured} is not Collection or OrderedCollection`); // Resolve to Object(may be Note) arrays const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems; diff --git a/packages/backend/src/misc/FileWriterStream.ts b/packages/backend/src/misc/FileWriterStream.ts index 27c67cb5df..a61d949ef4 100644 --- a/packages/backend/src/misc/FileWriterStream.ts +++ b/packages/backend/src/misc/FileWriterStream.ts @@ -21,7 +21,7 @@ export class FileWriterStream extends WritableStream { write: async (chunk, controller) => { if (file === null) { controller.error(); - throw new Error(); + throw new Error('file is null'); } await file.write(chunk); diff --git a/packages/backend/src/misc/fastify-reply-error.ts b/packages/backend/src/misc/fastify-reply-error.ts index e6c4e78d2f..03109e8b96 100644 --- a/packages/backend/src/misc/fastify-reply-error.ts +++ b/packages/backend/src/misc/fastify-reply-error.ts @@ -8,8 +8,8 @@ export class FastifyReplyError extends Error { public message: string; public statusCode: number; - constructor(statusCode: number, message: string) { - super(message); + constructor(statusCode: number, message: string, cause?: unknown) { + super(message, cause ? { cause } : undefined); this.message = message; this.statusCode = statusCode; } diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts index c0e8478db5..f0eba2d99c 100644 --- a/packages/backend/src/misc/id/aid.ts +++ b/packages/backend/src/misc/id/aid.ts @@ -8,6 +8,7 @@ import * as crypto from 'node:crypto'; import { parseBigInt36 } from '@/misc/bigint.js'; +import { IdentifiableError } from '../identifiable-error.js'; export const aidRegExp = /^[0-9a-z]{10}$/; @@ -26,7 +27,7 @@ function getNoise(): string { } export function genAid(t: number): string { - if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date'); + if (isNaN(t)) throw new IdentifiableError('6b73b7d5-9d2b-48b4-821c-ef955efe80ad', 'Failed to create AID: Invalid Date'); counter++; return getTime(t) + getNoise(); } diff --git a/packages/backend/src/misc/id/aidx.ts b/packages/backend/src/misc/id/aidx.ts index 006673a6d0..d2bb566e35 100644 --- a/packages/backend/src/misc/id/aidx.ts +++ b/packages/backend/src/misc/id/aidx.ts @@ -10,6 +10,7 @@ import { customAlphabet } from 'nanoid'; import { parseBigInt36 } from '@/misc/bigint.js'; +import { IdentifiableError } from '../identifiable-error.js'; export const aidxRegExp = /^[0-9a-z]{16}$/; @@ -34,7 +35,7 @@ function getNoise(): string { } export function genAidx(t: number): string { - if (isNaN(t)) throw new Error('Failed to create AIDX: Invalid Date'); + if (isNaN(t)) throw new IdentifiableError('6b73b7d5-9d2b-48b4-821c-ef955efe80ad', 'Failed to create AIDX: Invalid Date'); counter++; return getTime(t) + nodeId + getNoise(); } diff --git a/packages/backend/src/misc/identifiable-error.ts b/packages/backend/src/misc/identifiable-error.ts index 33ee7c3f25..56e13f2622 100644 --- a/packages/backend/src/misc/identifiable-error.ts +++ b/packages/backend/src/misc/identifiable-error.ts @@ -15,8 +15,8 @@ export class IdentifiableError extends Error { */ public readonly isRetryable: boolean; - constructor(id: string, message?: string, isRetryable = false, options?: ErrorOptions) { - super(message, options); + constructor(id: string, message?: string, isRetryable = false, cause?: unknown) { + super(message, cause ? { cause } : undefined); this.message = message ?? ''; this.id = id; this.isRetryable = isRetryable; diff --git a/packages/backend/src/misc/is-retryable-error.ts b/packages/backend/src/misc/is-retryable-error.ts index ed2a7ebee3..63b561b280 100644 --- a/packages/backend/src/misc/is-retryable-error.ts +++ b/packages/backend/src/misc/is-retryable-error.ts @@ -7,14 +7,26 @@ import { AbortError, FetchError } from 'node-fetch'; import { UnrecoverableError } from 'bullmq'; import { StatusError } from '@/misc/status-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { CaptchaError, captchaErrorCodes } from '@/core/CaptchaService.js'; +import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; +import { ConflictError } from '@/server/SkRateLimiterService.js'; /** * Returns false if the provided value represents a "permanent" error that cannot be retried. * Returns true if the error is retryable, unknown (as all errors are retryable by default), or not an error object. */ export function isRetryableError(e: unknown): boolean { + if (e instanceof AggregateError) return e.errors.every(inner => isRetryableError(inner)); if (e instanceof StatusError) return e.isRetryable; if (e instanceof IdentifiableError) return e.isRetryable; + if (e instanceof CaptchaError) { + if (e.code === captchaErrorCodes.verificationFailed) return false; + if (e.code === captchaErrorCodes.invalidParameters) return false; + if (e.code === captchaErrorCodes.invalidProvider) return false; + return true; + } + if (e instanceof FastifyReplyError) return false; + if (e instanceof ConflictError) return true; if (e instanceof UnrecoverableError) return false; if (e instanceof AbortError) return true; if (e instanceof FetchError) return true; diff --git a/packages/backend/src/misc/render-inline-error.ts b/packages/backend/src/misc/render-inline-error.ts index 3d1bd7746d..ccd4f524bc 100644 --- a/packages/backend/src/misc/render-inline-error.ts +++ b/packages/backend/src/misc/render-inline-error.ts @@ -5,23 +5,35 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import { StatusError } from '@/misc/status-error.js'; +import { CaptchaError } from '@/core/CaptchaService.js'; export function renderInlineError(err: unknown): string { - if (err instanceof Error) { - const text = printError(err); + const parts: string[] = []; + renderTo(err, parts); + return parts.join(''); +} - if (err.cause) { - const cause = renderInlineError(err.cause); - return `${text} [caused by]: ${cause}`; - } else { - return text; +function renderTo(err: unknown, parts: string[]): void { + parts.push(printError(err)); + + if (err instanceof AggregateError) { + for (let i = 0; i < err.errors.length; i++) { + parts.push(` [${i + 1}/${err.errors.length}]: `); + renderTo(err.errors[i], parts); } } - return String(err); + if (err instanceof Error) { + if (err.cause) { + parts.push(' [caused by]: '); + renderTo(err.cause, parts); + // const cause = renderInlineError(err.cause); + // parts.push(' [caused by]: ', cause); + } + } } -function printError(err: Error): string { +function printError(err: unknown): string { if (err instanceof IdentifiableError) { if (err.message) { return `${err.name} ${err.id}: ${err.message}`; @@ -40,9 +52,21 @@ function printError(err: Error): string { } } - if (err.message) { - return `${err.name}: ${err.message}`; - } else { - return err.name; + if (err instanceof CaptchaError) { + if (err.code.description) { + return `${err.name} ${err.code.description}: ${err.message}`; + } else { + return `${err.name}: ${err.message}`; + } + } + + if (err instanceof Error) { + if (err.message) { + return `${err.name}: ${err.message}`; + } else { + return err.name; + } } + + return String(err); } diff --git a/packages/backend/src/misc/status-error.ts b/packages/backend/src/misc/status-error.ts index 2c70d28f8d..4fd3bfcafb 100644 --- a/packages/backend/src/misc/status-error.ts +++ b/packages/backend/src/misc/status-error.ts @@ -9,8 +9,8 @@ export class StatusError extends Error { public isClientError: boolean; public isRetryable: boolean; - constructor(message: string, statusCode: number, statusMessage?: string, options?: ErrorOptions) { - super(message, options); + constructor(message: string, statusCode: number, statusMessage?: string, cause?: unknown) { + super(message, cause ? { cause } : undefined); this.name = 'StatusError'; this.statusCode = statusCode; this.statusMessage = statusMessage; diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 5a16496011..fc4c8bb814 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -133,23 +133,18 @@ export class DeliverProcessorService { } }); - if (res instanceof StatusError) { + if (res instanceof StatusError && !res.isRetryable) { // 4xx - if (!res.isRetryable) { - // 相手が閉鎖していることを明示しているため、配送停止する - if (job.data.isSharedInbox && res.statusCode === 410) { - this.federatedInstanceService.fetchOrRegister(host).then(i => { - this.federatedInstanceService.update(i.id, { - suspensionState: 'goneSuspended', - }); + // 相手が閉鎖していることを明示しているため、配送停止する + if (job.data.isSharedInbox && res.statusCode === 410) { + this.federatedInstanceService.fetchOrRegister(host).then(i => { + this.federatedInstanceService.update(i.id, { + suspensionState: 'goneSuspended', }); - throw new Bull.UnrecoverableError(`${host} is gone`); - } - throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); + }); + throw new Bull.UnrecoverableError(`${host} is gone`); } - - // 5xx etc. - throw new Error(`${res.statusCode} ${res.statusMessage}`); + throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); } else { // DNS error, socket error, timeout ... throw res; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index a069d390db..612b16dfbf 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -228,7 +228,7 @@ export class InboxProcessorService implements OnApplicationShutdown { const ldHost = this.utilityService.extractDbHost(authUser.user.uri); if (!this.utilityService.isFederationAllowedHost(ldHost)) { - throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`); + throw new Bull.UnrecoverableError(`skip: request host is blocked: ${ldHost}`); } } else { throw new Bull.UnrecoverableError(`skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`); diff --git a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts index d96199c201..73088f3312 100644 --- a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts +++ b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts @@ -133,7 +133,7 @@ export class ScheduleNotePostProcessorService { reason: renderInlineError(err), }); await this.noteScheduleRepository.remove(data); - this.logger.error(`Scheduled note failed:`, err); + this.logger.error(`Scheduled note failed: ${renderInlineError(err)}`); throw err; }); await this.noteScheduleRepository.remove(data); diff --git a/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts index 61042ff718..f9fcd1e928 100644 --- a/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts @@ -71,14 +71,9 @@ export class SystemWebhookDeliverProcessorService { latestStatus: res instanceof StatusError ? res.statusCode : 1, }); - if (res instanceof StatusError) { + if (res instanceof StatusError && !res.isRetryable) { // 4xx - if (!res.isRetryable) { - throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); - } - - // 5xx etc. - throw new Error(`${res.statusCode} ${res.statusMessage}`); + throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); } else { // DNS error, socket error, timeout ... throw res; diff --git a/packages/backend/src/queue/processors/UserWebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/UserWebhookDeliverProcessorService.ts index 9ec630ef70..0208ce6038 100644 --- a/packages/backend/src/queue/processors/UserWebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/UserWebhookDeliverProcessorService.ts @@ -69,14 +69,9 @@ export class UserWebhookDeliverProcessorService { latestStatus: res instanceof StatusError ? res.statusCode : 1, }); - if (res instanceof StatusError) { + if (res instanceof StatusError && !res.isRetryable) { // 4xx - if (!res.isRetryable) { - throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); - } - - // 5xx etc. - throw new Error(`${res.statusCode} ${res.statusMessage}`); + throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); } else { // DNS error, socket error, timeout ... throw res; diff --git a/packages/backend/src/server/SkRateLimiterService.ts b/packages/backend/src/server/SkRateLimiterService.ts index 8978318045..35e87b0fe8 100644 --- a/packages/backend/src/server/SkRateLimiterService.ts +++ b/packages/backend/src/server/SkRateLimiterService.ts @@ -389,7 +389,7 @@ function createLimitKey(limit: ParsedLimit, actor: string, value: string): strin return `rl_${actor}_${limit.key}_${value}`; } -class ConflictError extends Error {} +export class ConflictError extends Error {} interface LimitCounter { timestamp: number; diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 7f371ea309..a53fec88d0 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -205,37 +205,37 @@ export class SigninApiService { if (process.env.NODE_ENV !== 'test') { if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) { await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) { await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableFC && this.meta.fcSecretKey) { await this.captchaService.verifyFriendlyCaptcha(this.meta.fcSecretKey, body['frc-captcha-solution']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) { await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) { await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableTestcaptcha) { await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } } diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index cb71047a24..81e3a5b706 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -83,37 +83,37 @@ export class SignupApiService { if (process.env.NODE_ENV !== 'test') { if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) { await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) { await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) { await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) { await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableFC && this.meta.fcSecretKey) { await this.captchaService.verifyFriendlyCaptcha(this.meta.fcSecretKey, body['frc-captcha-solution']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } if (this.meta.enableTestcaptcha) { await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => { - throw new FastifyReplyError(400, err); + throw new FastifyReplyError(400, String(err), err); }); } } @@ -287,7 +287,7 @@ export class SignupApiService { token: secret, }; } catch (err) { - throw new FastifyReplyError(400, typeof err === 'string' ? err : (err as Error).toString()); + throw new FastifyReplyError(400, String(err), err); } } } @@ -356,7 +356,7 @@ export class SignupApiService { return this.signinService.signin(request, reply, account as MiLocalUser); } catch (err) { - throw new FastifyReplyError(400, typeof err === 'string' ? err : (err as Error).toString()); + throw new FastifyReplyError(400, String(err), err); } } } diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts index 129f69aca9..4644a069ee 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -68,11 +68,8 @@ export default class extends Endpoint { // eslint- private readonly moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { - try { - if (new URL(ps.inbox).protocol !== 'https:') throw new Error('https only'); - } catch { - throw new ApiError(meta.errors.invalidUrl); - } + if (!URL.canParse(ps.inbox)) throw new ApiError(meta.errors.invalidUrl); + if (new URL(ps.inbox).protocol !== 'https:') throw new ApiError(meta.errors.invalidUrl); await this.moderationLogService.log(me, 'addRelay', { inbox: ps.inbox, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 504a9c789e..08abd7fed5 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -72,7 +72,7 @@ export default class extends Endpoint { // eslint- ))).filter(x => x != null); if (files.length === 0) { - throw new Error(); + throw new Error('no files specified'); } const post = await this.galleryPostsRepository.insertOne(new MiGalleryPost({ diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 5243ee9603..d0f9b56863 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -73,7 +73,7 @@ export default class extends Endpoint { // eslint- ))).filter(x => x != null); if (files.length === 0) { - throw new Error(); + throw new Error('no files'); } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index d4098458d7..931c8d69b0 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -70,7 +70,7 @@ export default class extends Endpoint { // eslint- try { await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (e) { - throw new Error('authentication failed'); + throw new Error('authentication failed', { cause: e }); } } diff --git a/packages/backend/test/unit/misc/is-retryable-error.ts b/packages/backend/test/unit/misc/is-retryable-error.ts index 096bf64d4f..6d241066f7 100644 --- a/packages/backend/test/unit/misc/is-retryable-error.ts +++ b/packages/backend/test/unit/misc/is-retryable-error.ts @@ -8,6 +8,9 @@ import { AbortError } from 'node-fetch'; import { isRetryableError } from '@/misc/is-retryable-error.js'; import { StatusError } from '@/misc/status-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { CaptchaError, captchaErrorCodes } from '@/core/CaptchaService.js'; +import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; +import { ConflictError } from '@/server/SkRateLimiterService.js'; describe(isRetryableError, () => { it('should return true for retryable StatusError', () => { @@ -55,6 +58,78 @@ describe(isRetryableError, () => { expect(result).toBeTruthy(); }); + it('should return false for CaptchaError with verificationFailed', () => { + const error = new CaptchaError(captchaErrorCodes.verificationFailed, 'verificationFailed'); + const result = isRetryableError(error); + expect(result).toBeFalsy(); + }); + + it('should return false for CaptchaError with invalidProvider', () => { + const error = new CaptchaError(captchaErrorCodes.invalidProvider, 'invalidProvider'); + const result = isRetryableError(error); + expect(result).toBeFalsy(); + }); + + it('should return false for CaptchaError with invalidParameters', () => { + const error = new CaptchaError(captchaErrorCodes.invalidParameters, 'invalidParameters'); + const result = isRetryableError(error); + expect(result).toBeFalsy(); + }); + + it('should return true for CaptchaError with noResponseProvided', () => { + const error = new CaptchaError(captchaErrorCodes.noResponseProvided, 'noResponseProvided'); + const result = isRetryableError(error); + expect(result).toBeTruthy(); + }); + + it('should return true for CaptchaError with requestFailed', () => { + const error = new CaptchaError(captchaErrorCodes.requestFailed, 'requestFailed'); + const result = isRetryableError(error); + expect(result).toBeTruthy(); + }); + + it('should return true for CaptchaError with unknown', () => { + const error = new CaptchaError(captchaErrorCodes.unknown, 'unknown'); + const result = isRetryableError(error); + expect(result).toBeTruthy(); + }); + + it('should return true for CaptchaError with any other', () => { + const error = new CaptchaError(Symbol('temp'), 'unknown'); + const result = isRetryableError(error); + expect(result).toBeTruthy(); + }); + + it('should return false for FastifyReplyError', () => { + const error = new FastifyReplyError(400, 'test error'); + const result = isRetryableError(error); + expect(result).toBeFalsy(); + }); + + it('should return true for ConflictError', () => { + const error = new ConflictError('test error'); + const result = isRetryableError(error); + expect(result).toBeTruthy(); + }); + + it('should return true for AggregateError when all inners are retryable', () => { + const error = new AggregateError([ + new ConflictError(), + new ConflictError(), + ]); + const result = isRetryableError(error); + expect(result).toBeTruthy(); + }); + + it('should return true for AggregateError when any error is not retryable', () => { + const error = new AggregateError([ + new ConflictError(), + new StatusError('test err', 400), + ]); + const result = isRetryableError(error); + expect(result).toBeFalsy(); + }); + const nonErrorInputs = [ [null, 'null'], [undefined, 'undefined'], -- cgit v1.2.3-freya From 735cd12654e6f9600a06432a8ef6f743a6536b42 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 7 Jun 2025 18:59:02 -0400 Subject: fix array length check in i/update attributionDomains parameter --- packages/backend/src/server/api/endpoints/i/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 5a160d632c..7c13503f9b 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -270,7 +270,7 @@ export const paramDef = { minLength: 1, maxLength: 128, }, - maxLength: 32, + maxItems: 32, }, }, } as const; -- cgit v1.2.3-freya From 16858cf2f78ab781f1f08ca3d7bbffec27ece35c Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 7 Jun 2025 20:22:40 -0400 Subject: fix response type of i/registry/get endpoint --- packages/backend/src/server/api/endpoints/i/registry/get.ts | 4 +--- packages/misskey-js/src/autogen/types.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index dff33016e0..d284334834 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -20,9 +20,7 @@ export const meta = { }, }, - res: { - type: 'object', - }, + res: {}, // 10 calls per 5 seconds limit: { diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 82d18e2404..c09901c214 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -24267,7 +24267,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': Record; + 'application/json': unknown; }; }; /** @description Client error */ -- cgit v1.2.3-freya From 46a6612dc0e5eaa470170031012ae247f7a5eec5 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 5 Jun 2025 13:16:23 -0400 Subject: convert many RedisKVCaches to QuantumKVCache or MemoryKVCache --- packages/backend/src/core/CacheService.ts | 104 +++++++++++---------- .../backend/src/core/ChannelFollowingService.ts | 30 +++--- .../backend/src/core/PushNotificationService.ts | 19 ++-- packages/backend/src/core/UserBlockingService.ts | 12 ++- packages/backend/src/core/UserFollowingService.ts | 12 ++- packages/backend/src/core/UserKeypairService.ts | 14 +-- packages/backend/src/core/UserListService.ts | 34 ++++--- packages/backend/src/core/UserMutingService.ts | 9 +- .../backend/src/core/UserRenoteMutingService.ts | 9 +- .../src/server/api/endpoints/admin/nsfw-user.ts | 2 +- .../backend/src/server/api/endpoints/i/update.ts | 2 +- .../src/server/api/endpoints/sw/register.ts | 2 +- .../src/server/api/endpoints/sw/unregister.ts | 2 +- .../server/api/endpoints/sw/update-registration.ts | 2 +- 14 files changed, 126 insertions(+), 127 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 1cf63221f9..f04b18c02b 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -7,12 +7,13 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { IsNull } from 'typeorm'; import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing, MiNote } from '@/models/_.js'; -import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; +import { MemoryKVCache, QuantumKVCache, RedisKVCache } from '@/misc/cache.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; -import type { GlobalEvents } from '@/core/GlobalEventService.js'; +import type { GlobalEvents, InternalEventTypes } from '@/core/GlobalEventService.js'; +import { InternalEventService } from '@/core/InternalEventService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; export interface FollowStats { @@ -39,12 +40,12 @@ export class CacheService implements OnApplicationShutdown { public localUserByNativeTokenCache: MemoryKVCache; public localUserByIdCache: MemoryKVCache; public uriPersonCache: MemoryKVCache; - public userProfileCache: RedisKVCache; - public userMutingsCache: RedisKVCache>; - public userBlockingCache: RedisKVCache>; - public userBlockedCache: RedisKVCache>; // NOTE: 「被」Blockキャッシュ - public renoteMutingsCache: RedisKVCache>; - public userFollowingsCache: RedisKVCache | undefined>>; + public userProfileCache: QuantumKVCache; + public userMutingsCache: QuantumKVCache>; + public userBlockingCache: QuantumKVCache>; + public userBlockedCache: QuantumKVCache>; // NOTE: 「被」Blockキャッシュ + public renoteMutingsCache: QuantumKVCache>; + public userFollowingsCache: QuantumKVCache | undefined>>; private readonly userFollowStatsCache = new MemoryKVCache(1000 * 60 * 10); // 10 minutes private readonly translationsCache: RedisKVCache; @@ -74,6 +75,7 @@ export class CacheService implements OnApplicationShutdown { private followingsRepository: FollowingsRepository, private userEntityService: UserEntityService, + private readonly internalEventService: InternalEventService, ) { //this.onMessage = this.onMessage.bind(this); @@ -82,49 +84,33 @@ export class CacheService implements OnApplicationShutdown { this.localUserByIdCache = new MemoryKVCache(1000 * 60 * 5); // 5m this.uriPersonCache = new MemoryKVCache(1000 * 60 * 5); // 5m - this.userProfileCache = new RedisKVCache(this.redisClient, 'userProfile', { + this.userProfileCache = new QuantumKVCache(this.internalEventService, 'userProfile', { lifetime: 1000 * 60 * 30, // 30m - memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.userProfilesRepository.findOneByOrFail({ userId: key }), - toRedisConverter: (value) => JSON.stringify(value), - fromRedisConverter: (value) => JSON.parse(value), // TODO: date型の考慮 }); - this.userMutingsCache = new RedisKVCache>(this.redisClient, 'userMutings', { + this.userMutingsCache = new QuantumKVCache>(this.internalEventService, 'userMutings', { lifetime: 1000 * 60 * 30, // 30m - memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))), - toRedisConverter: (value) => JSON.stringify(Array.from(value)), - fromRedisConverter: (value) => new Set(JSON.parse(value)), }); - this.userBlockingCache = new RedisKVCache>(this.redisClient, 'userBlocking', { + this.userBlockingCache = new QuantumKVCache>(this.internalEventService, 'userBlocking', { lifetime: 1000 * 60 * 30, // 30m - memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.blockingsRepository.find({ where: { blockerId: key }, select: ['blockeeId'] }).then(xs => new Set(xs.map(x => x.blockeeId))), - toRedisConverter: (value) => JSON.stringify(Array.from(value)), - fromRedisConverter: (value) => new Set(JSON.parse(value)), }); - this.userBlockedCache = new RedisKVCache>(this.redisClient, 'userBlocked', { + this.userBlockedCache = new QuantumKVCache>(this.internalEventService, 'userBlocked', { lifetime: 1000 * 60 * 30, // 30m - memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.blockingsRepository.find({ where: { blockeeId: key }, select: ['blockerId'] }).then(xs => new Set(xs.map(x => x.blockerId))), - toRedisConverter: (value) => JSON.stringify(Array.from(value)), - fromRedisConverter: (value) => new Set(JSON.parse(value)), }); - this.renoteMutingsCache = new RedisKVCache>(this.redisClient, 'renoteMutings', { + this.renoteMutingsCache = new QuantumKVCache>(this.internalEventService, 'renoteMutings', { lifetime: 1000 * 60 * 30, // 30m - memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.renoteMutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))), - toRedisConverter: (value) => JSON.stringify(Array.from(value)), - fromRedisConverter: (value) => new Set(JSON.parse(value)), }); - this.userFollowingsCache = new RedisKVCache | undefined>>(this.redisClient, 'userFollowings', { + this.userFollowingsCache = new QuantumKVCache | undefined>>(this.internalEventService, 'userFollowings', { lifetime: 1000 * 60 * 30, // 30m - memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId', 'withReplies'] }).then(xs => { const obj: Record | undefined> = {}; for (const x of xs) { @@ -132,8 +118,6 @@ export class CacheService implements OnApplicationShutdown { } return obj; }), - toRedisConverter: (value) => JSON.stringify(value), - fromRedisConverter: (value) => JSON.parse(value), }); this.translationsCache = new RedisKVCache(this.redisClient, 'translations', { @@ -143,20 +127,21 @@ export class CacheService implements OnApplicationShutdown { // NOTE: チャンネルのフォロー状況キャッシュはChannelFollowingServiceで行っている - this.redisForSub.on('message', this.onMessage); + this.internalEventService.on('userChangeSuspendedState', this.onUserEvent); + this.internalEventService.on('userChangeDeletedState', this.onUserEvent); + this.internalEventService.on('remoteUserUpdated', this.onUserEvent); + this.internalEventService.on('localUserUpdated', this.onUserEvent); + this.internalEventService.on('userChangeSuspendedState', this.onUserEvent); + this.internalEventService.on('userTokenRegenerated', this.onTokenEvent); + this.internalEventService.on('follow', this.onFollowEvent); + this.internalEventService.on('unfollow', this.onFollowEvent); } @bindThis - private async onMessage(_: string, data: string): Promise { - const obj = JSON.parse(data); - - if (obj.channel === 'internal') { - const { type, body } = obj.message as GlobalEvents['internal']['payload']; - switch (type) { - case 'userChangeSuspendedState': - case 'userChangeDeletedState': - case 'remoteUserUpdated': - case 'localUserUpdated': { + private async onUserEvent(body: InternalEventTypes[E]): Promise { + { + { + { const user = await this.usersRepository.findOneBy({ id: body.id }); if (user == null) { this.userByIdCache.delete(body.id); @@ -178,20 +163,32 @@ export class CacheService implements OnApplicationShutdown { this.localUserByIdCache.set(user.id, user); } } - break; } - case 'userTokenRegenerated': { + } + } + } + + private async onTokenEvent(body: InternalEventTypes[E]): Promise { + { + { + { const user = await this.usersRepository.findOneByOrFail({ id: body.id }) as MiLocalUser; this.localUserByNativeTokenCache.delete(body.oldToken); this.localUserByNativeTokenCache.set(body.newToken, user); - break; } + } + } + } + + private async onFollowEvent(body: InternalEventTypes[E], type: E): Promise { + { + switch (type) { case 'follow': { const follower = this.userByIdCache.get(body.followerId); if (follower) follower.followingCount++; const followee = this.userByIdCache.get(body.followeeId); if (followee) followee.followersCount++; - this.userFollowingsCache.delete(body.followerId); + await this.userFollowingsCache.delete(body.followerId); this.userFollowStatsCache.delete(body.followerId); this.userFollowStatsCache.delete(body.followeeId); break; @@ -201,13 +198,11 @@ export class CacheService implements OnApplicationShutdown { if (follower) follower.followingCount--; const followee = this.userByIdCache.get(body.followeeId); if (followee) followee.followersCount--; - this.userFollowingsCache.delete(body.followerId); + await this.userFollowingsCache.delete(body.followerId); this.userFollowStatsCache.delete(body.followerId); this.userFollowStatsCache.delete(body.followeeId); break; } - default: - break; } } } @@ -300,7 +295,14 @@ export class CacheService implements OnApplicationShutdown { @bindThis public dispose(): void { - this.redisForSub.off('message', this.onMessage); + this.internalEventService.off('userChangeSuspendedState', this.onUserEvent); + this.internalEventService.off('userChangeDeletedState', this.onUserEvent); + this.internalEventService.off('remoteUserUpdated', this.onUserEvent); + this.internalEventService.off('localUserUpdated', this.onUserEvent); + this.internalEventService.off('userChangeSuspendedState', this.onUserEvent); + this.internalEventService.off('userTokenRegenerated', this.onTokenEvent); + this.internalEventService.off('follow', this.onFollowEvent); + this.internalEventService.off('unfollow', this.onFollowEvent); this.userByIdCache.dispose(); this.localUserByNativeTokenCache.dispose(); this.localUserByIdCache.dispose(); diff --git a/packages/backend/src/core/ChannelFollowingService.ts b/packages/backend/src/core/ChannelFollowingService.ts index 12251595e2..869456998b 100644 --- a/packages/backend/src/core/ChannelFollowingService.ts +++ b/packages/backend/src/core/ChannelFollowingService.ts @@ -9,14 +9,16 @@ import { DI } from '@/di-symbols.js'; import type { ChannelFollowingsRepository } from '@/models/_.js'; import { MiChannel } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js'; +import { GlobalEvents, GlobalEventService, InternalEventTypes } from '@/core/GlobalEventService.js'; import { bindThis } from '@/decorators.js'; import type { MiLocalUser } from '@/models/User.js'; -import { RedisKVCache } from '@/misc/cache.js'; +import { QuantumKVCache, RedisKVCache } from '@/misc/cache.js'; +import { InternalEventService } from './InternalEventService.js'; @Injectable() export class ChannelFollowingService implements OnModuleInit { - public userFollowingChannelsCache: RedisKVCache>; + // TODO check for regs + public userFollowingChannelsCache: QuantumKVCache>; constructor( @Inject(DI.redis) @@ -27,19 +29,18 @@ export class ChannelFollowingService implements OnModuleInit { private channelFollowingsRepository: ChannelFollowingsRepository, private idService: IdService, private globalEventService: GlobalEventService, + private readonly internalEventService: InternalEventService, ) { - this.userFollowingChannelsCache = new RedisKVCache>(this.redisClient, 'userFollowingChannels', { + this.userFollowingChannelsCache = new QuantumKVCache>(this.internalEventService, 'userFollowingChannels', { lifetime: 1000 * 60 * 30, // 30m - memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.channelFollowingsRepository.find({ where: { followerId: key }, select: ['followeeId'], }).then(xs => new Set(xs.map(x => x.followeeId))), - toRedisConverter: (value) => JSON.stringify(Array.from(value)), - fromRedisConverter: (value) => new Set(JSON.parse(value)), }); - this.redisForSub.on('message', this.onMessage); + this.internalEventService.on('followChannel', this.onMessage); + this.internalEventService.on('unfollowChannel', this.onMessage); } onModuleInit() { @@ -79,18 +80,15 @@ export class ChannelFollowingService implements OnModuleInit { } @bindThis - private async onMessage(_: string, data: string): Promise { - const obj = JSON.parse(data); - - if (obj.channel === 'internal') { - const { type, body } = obj.message as GlobalEvents['internal']['payload']; + private async onMessage(body: InternalEventTypes[E], type: E): Promise { + { switch (type) { case 'followChannel': { - this.userFollowingChannelsCache.refresh(body.userId); + await this.userFollowingChannelsCache.delete(body.userId); break; } case 'unfollowChannel': { - this.userFollowingChannelsCache.delete(body.userId); + await this.userFollowingChannelsCache.delete(body.userId); break; } } @@ -99,6 +97,8 @@ export class ChannelFollowingService implements OnModuleInit { @bindThis public dispose(): void { + this.internalEventService.off('followChannel', this.onMessage); + this.internalEventService.off('unfollowChannel', this.onMessage); this.userFollowingChannelsCache.dispose(); } diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 9333c1ebc5..38bc5e3901 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -12,7 +12,8 @@ import type { Packed } from '@/misc/json-schema.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import type { MiMeta, MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; -import { RedisKVCache } from '@/misc/cache.js'; +import { QuantumKVCache, RedisKVCache } from '@/misc/cache.js'; +import { InternalEventService } from '@/core/InternalEventService.js'; // Defined also packages/sw/types.ts#L13 type PushNotificationsTypes = { @@ -48,7 +49,7 @@ function truncateBody(type: T, body: Pus @Injectable() export class PushNotificationService implements OnApplicationShutdown { - private subscriptionsCache: RedisKVCache; + private subscriptionsCache: QuantumKVCache; constructor( @Inject(DI.config) @@ -62,13 +63,11 @@ export class PushNotificationService implements OnApplicationShutdown { @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, + private readonly internalEventService: InternalEventService, ) { - this.subscriptionsCache = new RedisKVCache(this.redisClient, 'userSwSubscriptions', { + this.subscriptionsCache = new QuantumKVCache(this.internalEventService, 'userSwSubscriptions', { lifetime: 1000 * 60 * 60 * 1, // 1h - memoryCacheLifetime: 1000 * 60 * 3, // 3m fetcher: (key) => this.swSubscriptionsRepository.findBy({ userId: key }), - toRedisConverter: (value) => JSON.stringify(value), - fromRedisConverter: (value) => JSON.parse(value), }); } @@ -114,8 +113,8 @@ export class PushNotificationService implements OnApplicationShutdown { endpoint: subscription.endpoint, auth: subscription.auth, publickey: subscription.publickey, - }).then(() => { - this.refreshCache(userId); + }).then(async () => { + await this.refreshCache(userId); }); } }); @@ -123,8 +122,8 @@ export class PushNotificationService implements OnApplicationShutdown { } @bindThis - public refreshCache(userId: string): void { - this.subscriptionsCache.refresh(userId); + public async refreshCache(userId: string): Promise { + await this.subscriptionsCache.refresh(userId); } @bindThis diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 8da1bb2092..1a1e7c4778 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -77,8 +77,10 @@ export class UserBlockingService implements OnModuleInit { await this.blockingsRepository.insert(blocking); - this.cacheService.userBlockingCache.refresh(blocker.id); - this.cacheService.userBlockedCache.refresh(blockee.id); + await Promise.all([ + this.cacheService.userBlockingCache.delete(blocker.id), + this.cacheService.userBlockedCache.delete(blockee.id), + ]); this.globalEventService.publishInternalEvent('blockingCreated', { blockerId: blocker.id, @@ -168,8 +170,10 @@ export class UserBlockingService implements OnModuleInit { await this.blockingsRepository.delete(blocking.id); - this.cacheService.userBlockingCache.refresh(blocker.id); - this.cacheService.userBlockedCache.refresh(blockee.id); + await Promise.all([ + this.cacheService.userBlockingCache.delete(blocker.id), + this.cacheService.userBlockedCache.delete(blockee.id), + ]); this.globalEventService.publishInternalEvent('blockingDeleted', { blockerId: blocker.id, diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 897b950022..6a6c9a3000 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -29,6 +29,7 @@ import { AccountMoveService } from '@/core/AccountMoveService.js'; import { UtilityService } from '@/core/UtilityService.js'; import type { ThinUser } from '@/queue/types.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { InternalEventService } from '@/core/InternalEventService.js'; import type Logger from '../logger.js'; type Local = MiLocalUser | { @@ -86,6 +87,7 @@ export class UserFollowingService implements OnModuleInit { private accountMoveService: AccountMoveService, private perUserFollowingChart: PerUserFollowingChart, private instanceChart: InstanceChart, + private readonly internalEventService: InternalEventService, loggerService: LoggerService, ) { @@ -264,7 +266,8 @@ export class UserFollowingService implements OnModuleInit { } }); - this.cacheService.userFollowingsCache.refresh(follower.id); + // Handled by CacheService + //this.cacheService.userFollowingsCache.refresh(follower.id); const requestExist = await this.followRequestsRepository.exists({ where: { @@ -291,7 +294,7 @@ export class UserFollowingService implements OnModuleInit { }, followee.id); } - this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id }); + await this.internalEventService.emit('follow', { followerId: follower.id, followeeId: followee.id }); const [followeeUser, followerUser] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: followee.id }), @@ -381,7 +384,8 @@ export class UserFollowingService implements OnModuleInit { await this.followingsRepository.delete(following.id); - this.cacheService.userFollowingsCache.refresh(follower.id); + // Handled by CacheService + // this.cacheService.userFollowingsCache.refresh(follower.id); this.decrementFollowing(following.follower, following.followee); @@ -412,7 +416,7 @@ export class UserFollowingService implements OnModuleInit { follower: MiUser, followee: MiUser, ): Promise { - this.globalEventService.publishInternalEvent('unfollow', { followerId: follower.id, followeeId: followee.id }); + await this.internalEventService.emit('unfollow', { followerId: follower.id, followeeId: followee.id }); // Neither followee nor follower has moved. if (!follower.movedToUri && !followee.movedToUri) { diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts index 92d61cd103..d8a67d273b 100644 --- a/packages/backend/src/core/UserKeypairService.ts +++ b/packages/backend/src/core/UserKeypairService.ts @@ -7,14 +7,14 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Redis from 'ioredis'; import type { MiUser } from '@/models/User.js'; import type { UserKeypairsRepository } from '@/models/_.js'; -import { RedisKVCache } from '@/misc/cache.js'; +import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; import type { MiUserKeypair } from '@/models/UserKeypair.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; @Injectable() export class UserKeypairService implements OnApplicationShutdown { - private cache: RedisKVCache; + private cache: MemoryKVCache; constructor( @Inject(DI.redis) @@ -23,18 +23,12 @@ export class UserKeypairService implements OnApplicationShutdown { @Inject(DI.userKeypairsRepository) private userKeypairsRepository: UserKeypairsRepository, ) { - this.cache = new RedisKVCache(this.redisClient, 'userKeypair', { - lifetime: 1000 * 60 * 60 * 24, // 24h - memoryCacheLifetime: 1000 * 60 * 60, // 1h - fetcher: (key) => this.userKeypairsRepository.findOneByOrFail({ userId: key }), - toRedisConverter: (value) => JSON.stringify(value), - fromRedisConverter: (value) => JSON.parse(value), - }); + this.cache = new MemoryKVCache(1000 * 60 * 60 * 24); // 24h } @bindThis public async getUserKeypair(userId: MiUser['id']): Promise { - return await this.cache.fetch(userId); + return await this.cache.fetch(userId, () => this.userKeypairsRepository.findOneByOrFail({ userId })); } @bindThis diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index e7200ab1bf..0240184d13 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -11,21 +11,22 @@ import type { MiUser } from '@/models/User.js'; import type { MiUserList } from '@/models/UserList.js'; import type { MiUserListMembership } from '@/models/UserListMembership.js'; import { IdService } from '@/core/IdService.js'; -import type { GlobalEvents } from '@/core/GlobalEventService.js'; +import type { GlobalEvents, InternalEventTypes } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { QueueService } from '@/core/QueueService.js'; -import { RedisKVCache } from '@/misc/cache.js'; +import { QuantumKVCache, RedisKVCache } from '@/misc/cache.js'; import { RoleService } from '@/core/RoleService.js'; import { SystemAccountService } from '@/core/SystemAccountService.js'; +import { InternalEventService } from '@/core/InternalEventService.js'; @Injectable() export class UserListService implements OnApplicationShutdown, OnModuleInit { public static TooManyUsersError = class extends Error {}; - public membersCache: RedisKVCache>; + public membersCache: QuantumKVCache>; private roleService: RoleService; constructor( @@ -48,16 +49,15 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit { private globalEventService: GlobalEventService, private queueService: QueueService, private systemAccountService: SystemAccountService, + private readonly internalEventService: InternalEventService, ) { - this.membersCache = new RedisKVCache>(this.redisClient, 'userListMembers', { + this.membersCache = new QuantumKVCache>(this.internalEventService, 'userListMembers', { lifetime: 1000 * 60 * 30, // 30m - memoryCacheLifetime: 1000 * 60, // 1m fetcher: (key) => this.userListMembershipsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))), - toRedisConverter: (value) => JSON.stringify(Array.from(value)), - fromRedisConverter: (value) => new Set(JSON.parse(value)), }); - this.redisForSub.on('message', this.onMessage); + this.internalEventService.on('userListMemberAdded', this.onMessage); + this.internalEventService.on('userListMemberRemoved', this.onMessage); } async onModuleInit() { @@ -65,24 +65,21 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit { } @bindThis - private async onMessage(_: string, data: string): Promise { - const obj = JSON.parse(data); - - if (obj.channel === 'internal') { - const { type, body } = obj.message as GlobalEvents['internal']['payload']; + private async onMessage(body: InternalEventTypes[E], type: E): Promise { + { switch (type) { case 'userListMemberAdded': { const { userListId, memberId } = body; - const members = await this.membersCache.get(userListId); - if (members) { + if (this.membersCache.has(userListId)) { + const members = await this.membersCache.get(userListId); members.add(memberId); } break; } case 'userListMemberRemoved': { const { userListId, memberId } = body; - const members = await this.membersCache.get(userListId); - if (members) { + if (this.membersCache.has(userListId)) { + const members = await this.membersCache.get(userListId); members.delete(memberId); } break; @@ -150,7 +147,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit { @bindThis public dispose(): void { - this.redisForSub.off('message', this.onMessage); + this.internalEventService.off('userListMemberAdded', this.onMessage); + this.internalEventService.off('userListMemberRemoved', this.onMessage); this.membersCache.dispose(); } diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 06643be5fb..4f72c1863b 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -32,7 +32,7 @@ export class UserMutingService { muteeId: target.id, }); - this.cacheService.userMutingsCache.refresh(user.id); + await this.cacheService.userMutingsCache.delete(user.id); } @bindThis @@ -43,9 +43,8 @@ export class UserMutingService { id: In(mutings.map(m => m.id)), }); - const muterIds = [...new Set(mutings.map(m => m.muterId))]; - for (const muterId of muterIds) { - this.cacheService.userMutingsCache.refresh(muterId); - } + await Promise.all(Array + .from(new Set(mutings.map(m => m.muterId))) + .map(muterId => this.cacheService.userMutingsCache.delete(muterId))); } } diff --git a/packages/backend/src/core/UserRenoteMutingService.ts b/packages/backend/src/core/UserRenoteMutingService.ts index bdc5e23f4b..9d5ec164c8 100644 --- a/packages/backend/src/core/UserRenoteMutingService.ts +++ b/packages/backend/src/core/UserRenoteMutingService.ts @@ -33,7 +33,7 @@ export class UserRenoteMutingService { muteeId: target.id, }); - await this.cacheService.renoteMutingsCache.refresh(user.id); + await this.cacheService.renoteMutingsCache.delete(user.id); } @bindThis @@ -44,9 +44,8 @@ export class UserRenoteMutingService { id: In(mutings.map(m => m.id)), }); - const muterIds = [...new Set(mutings.map(m => m.muterId))]; - for (const muterId of muterIds) { - await this.cacheService.renoteMutingsCache.refresh(muterId); - } + await Promise.all(Array + .from(new Set(mutings.map(m => m.muterId))) + .map(muterId => this.cacheService.renoteMutingsCache.delete(muterId))); } } diff --git a/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts b/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts index 194e793eda..f6c4f0b635 100644 --- a/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts @@ -47,7 +47,7 @@ export default class extends Endpoint { // eslint- alwaysMarkNsfw: true, }); - await this.cacheService.userProfileCache.refresh(ps.userId); + await this.cacheService.userProfileCache.delete(ps.userId); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index dda42ce0e4..e632915f62 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -617,7 +617,7 @@ export default class extends Endpoint { // eslint- const updatedProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - this.cacheService.userProfileCache.set(user.id, updatedProfile); + await this.cacheService.userProfileCache.set(user.id, updatedProfile); // Publish meUpdated event this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj); diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index f447b5598b..2f72e6ce1d 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -104,7 +104,7 @@ export default class extends Endpoint { // eslint- sendReadMessage: ps.sendReadMessage, }); - this.pushNotificationService.refreshCache(me.id); + await this.pushNotificationService.refreshCache(me.id); return { state: 'subscribed' as const, diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index aa7e03dceb..f43a2cce28 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -46,7 +46,7 @@ export default class extends Endpoint { // eslint- }); if (me) { - this.pushNotificationService.refreshCache(me.id); + await this.pushNotificationService.refreshCache(me.id); } }); } diff --git a/packages/backend/src/server/api/endpoints/sw/update-registration.ts b/packages/backend/src/server/api/endpoints/sw/update-registration.ts index 78b9323b7b..0cbed273e8 100644 --- a/packages/backend/src/server/api/endpoints/sw/update-registration.ts +++ b/packages/backend/src/server/api/endpoints/sw/update-registration.ts @@ -86,7 +86,7 @@ export default class extends Endpoint { // eslint- sendReadMessage: swSubscription.sendReadMessage, }); - this.pushNotificationService.refreshCache(me.id); + await this.pushNotificationService.refreshCache(me.id); return { userId: swSubscription.userId, -- cgit v1.2.3-freya From 7eca06d672604fe03ce10591263bd04cf58e54c6 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 23 May 2025 19:48:55 -0400 Subject: use pnpm package alias for @transfem-org/sfm-js --- packages/backend/package.json | 2 +- packages/backend/src/core/MfmService.ts | 2 +- packages/backend/src/core/NoteCreateService.ts | 2 +- packages/backend/src/core/NoteEditService.ts | 2 +- packages/backend/src/core/activitypub/ApMfmService.ts | 2 +- packages/backend/src/core/activitypub/ApRendererService.ts | 2 +- packages/backend/src/misc/extract-custom-emojis-from-mfm.ts | 2 +- packages/backend/src/misc/extract-hashtags.ts | 2 +- packages/backend/src/misc/extract-mentions.ts | 2 +- packages/backend/src/server/api/endpoints/i/update.ts | 2 +- packages/backend/src/server/api/mastodon/MastodonConverters.ts | 2 +- packages/backend/src/server/web/FeedService.ts | 2 +- packages/backend/test/unit/MfmService.ts | 2 +- packages/backend/test/unit/extract-mentions.ts | 2 +- packages/frontend-embed/package.json | 4 ++-- packages/frontend-embed/src/components/EmMfm.ts | 2 +- packages/frontend-embed/src/components/EmNote.vue | 2 +- packages/frontend-embed/src/components/EmNoteDetailed.vue | 2 +- packages/frontend/package.json | 2 +- packages/frontend/src/components/MkNote.vue | 2 +- packages/frontend/src/components/MkNoteDetailed.vue | 2 +- packages/frontend/src/components/MkPostForm.vue | 2 +- packages/frontend/src/components/MkSubNoteContent.vue | 2 +- packages/frontend/src/components/SkNote.vue | 2 +- packages/frontend/src/components/SkNoteDetailed.vue | 2 +- packages/frontend/src/components/SkOldNoteWindow.vue | 2 +- packages/frontend/src/components/global/MkMfm.ts | 2 +- packages/frontend/src/pages/chat/XMessage.vue | 2 +- packages/frontend/src/utility/check-animated-mfm.ts | 2 +- packages/frontend/src/utility/extract-mentions.ts | 2 +- packages/frontend/src/utility/extract-preview-urls.ts | 2 +- packages/frontend/src/utility/extract-url-from-mfm.ts | 2 +- 32 files changed, 33 insertions(+), 33 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/package.json b/packages/backend/package.json index 69d38f3bfb..70432d1bd1 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -92,7 +92,7 @@ "@smithy/node-http-handler": "2.5.0", "@swc/cli": "0.7.3", "@swc/core": "1.11.24", - "@transfem-org/sfm-js": "0.24.6", + "mfm-js": "npm:@transfem-org/sfm-js@0.24.6", "@twemoji/parser": "15.1.1", "accepts": "1.3.8", "ajv": "8.17.1", diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index d85ac7c807..551b25394a 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -15,7 +15,7 @@ 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 type * as mfm from '@transfem-org/sfm-js'; +import type * as mfm from 'mfm-js'; const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index a9f4083446..f8584a4a48 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -4,7 +4,7 @@ */ import { setImmediate } from 'node:timers/promises'; -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import { In, DataSource, IsNull, LessThan } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index a359381573..d963bf1945 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -4,7 +4,7 @@ */ import { setImmediate } from 'node:timers/promises'; -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import { DataSource, In, IsNull, LessThan } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index c4a948429a..ddb6461746 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -4,7 +4,7 @@ */ import { Injectable } from '@nestjs/common'; -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import { MfmService, Appender } from '@/core/MfmService.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 789611fd97..08a8f30049 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -6,7 +6,7 @@ import { createPublicKey, randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import { UnrecoverableError } from 'bullmq'; import { Element, Text } from 'domhandler'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts b/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts index 36a9b8e1f4..73ae9abb54 100644 --- a/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts +++ b/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import { unique } from '@/misc/prelude/array.js'; export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] { diff --git a/packages/backend/src/misc/extract-hashtags.ts b/packages/backend/src/misc/extract-hashtags.ts index ed7606d995..d3d245d414 100644 --- a/packages/backend/src/misc/extract-hashtags.ts +++ b/packages/backend/src/misc/extract-hashtags.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import { unique } from '@/misc/prelude/array.js'; export function extractHashtags(nodes: mfm.MfmNode[]): string[] { diff --git a/packages/backend/src/misc/extract-mentions.ts b/packages/backend/src/misc/extract-mentions.ts index bb21c32ffb..2ec9349718 100644 --- a/packages/backend/src/misc/extract-mentions.ts +++ b/packages/backend/src/misc/extract-mentions.ts @@ -5,7 +5,7 @@ // test is located in test/extract-mentions -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] { // TODO: 重複を削除 diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index e632915f62..5767880531 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; diff --git a/packages/backend/src/server/api/mastodon/MastodonConverters.ts b/packages/backend/src/server/api/mastodon/MastodonConverters.ts index 02ce31c4f8..df8d68042a 100644 --- a/packages/backend/src/server/api/mastodon/MastodonConverters.ts +++ b/packages/backend/src/server/api/mastodon/MastodonConverters.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Entity, MastodonEntity, MisskeyEntity } from 'megalodon'; -import mfm from '@transfem-org/sfm-js'; +import mfm from 'mfm-js'; import { MastodonNotificationType } from 'megalodon/lib/src/mastodon/notification.js'; import { NotificationType } from 'megalodon/lib/src/notification.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index dcd4d80303..a622ae7e34 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -15,7 +15,7 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import { MfmService } from "@/core/MfmService.js"; -import { parse as mfmParse } from '@transfem-org/sfm-js'; +import { parse as mfmParse } from 'mfm-js'; @Injectable() export class FeedService { diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts index f96f3977d0..af1fc4e132 100644 --- a/packages/backend/test/unit/MfmService.ts +++ b/packages/backend/test/unit/MfmService.ts @@ -4,7 +4,7 @@ */ import * as assert from 'assert'; -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import { Test } from '@nestjs/testing'; import { CoreModule } from '@/core/CoreModule.js'; diff --git a/packages/backend/test/unit/extract-mentions.ts b/packages/backend/test/unit/extract-mentions.ts index 2aad89d65b..3403387e30 100644 --- a/packages/backend/test/unit/extract-mentions.ts +++ b/packages/backend/test/unit/extract-mentions.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; -import { parse } from '@transfem-org/sfm-js'; +import { parse } from 'mfm-js'; import { extractMentions } from '@/misc/extract-mentions.js'; describe('Extract mentions', () => { diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 5191fe6852..5b4774c7af 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -11,8 +11,8 @@ }, "dependencies": { "@discordapp/twemoji": "15.1.0", - "@phosphor-icons/web": "^2.0.3", - "@transfem-org/sfm-js": "0.24.5", + "@phosphor-icons/web": "2.1.2", + "mfm-js": "npm:@transfem-org/sfm-js@0.24.6", "buraha": "0.0.1", "frontend-shared": "workspace:*", "json5": "2.2.3", diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts index d377d492e0..74ae3373ef 100644 --- a/packages/frontend-embed/src/components/EmMfm.ts +++ b/packages/frontend-embed/src/components/EmMfm.ts @@ -5,7 +5,7 @@ import { h, provide } from 'vue'; import type { VNode, SetupContext } from 'vue'; -import * as mfm from '@transfem-org/sfm-js'; +import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import { host } from '@@/js/config.js'; import EmUrl from '@/components/EmUrl.vue'; diff --git a/packages/frontend-embed/src/components/EmNote.vue b/packages/frontend-embed/src/components/EmNote.vue index 666cbde72d..0dc77d09a7 100644 --- a/packages/frontend-embed/src/components/EmNote.vue +++ b/packages/frontend-embed/src/components/EmNote.vue @@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only