diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76f9d7b613..4c0b370146 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -677,6 +677,9 @@ seems to do a decent job) `packages/frontend/src/pages/timeline.vue`, `packages/frontend/src/ui/deck/tl-column.vue`, `packages/frontend/src/widgets/WidgetTimeline.vue`) + * from `packages/backend/src/queue/processors/InboxProcessorService.ts` + to `packages/backend/src/core/UpdateInstanceQueue.ts` + where `updateInstanceQueue` is impacted * if there have been any changes to the federated user data (the `renderPerson` function in `packages/backend/src/core/activitypub/ApRendererService.ts`), make diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 3c35dfc4ff..997d81facc 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -19,6 +19,7 @@ import { TimeService } from '@/core/TimeService.js'; import { EnvService } from '@/core/EnvService.js'; import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js'; import { ApLogService } from '@/core/ApLogService.js'; +import { UpdateInstanceQueue } from '@/core/UpdateInstanceQueue.js'; import { AccountMoveService } from './AccountMoveService.js'; import { AccountUpdateService } from './AccountUpdateService.js'; import { AnnouncementService } from './AnnouncementService.js'; @@ -220,6 +221,7 @@ const $UserRenoteMutingService: Provider = { provide: 'UserRenoteMutingService', const $UserSearchService: Provider = { provide: 'UserSearchService', useExisting: UserSearchService }; const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService }; const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService }; +const $UpdateInstanceQueue: Provider = { provide: 'UpdateInstanceQueue', useExisting: UpdateInstanceQueue }; const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService }; const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService }; const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService }; @@ -378,6 +380,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp UserSearchService, UserSuspendService, UserAuthService, + UpdateInstanceQueue, VideoProcessingService, UserWebhookService, SystemWebhookService, @@ -532,6 +535,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp $UserSearchService, $UserSuspendService, $UserAuthService, + $UpdateInstanceQueue, $VideoProcessingService, $UserWebhookService, $SystemWebhookService, @@ -687,6 +691,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp UserSearchService, UserSuspendService, UserAuthService, + UpdateInstanceQueue, VideoProcessingService, UserWebhookService, SystemWebhookService, @@ -840,6 +845,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp $UserSearchService, $UserSuspendService, $UserAuthService, + $UpdateInstanceQueue, $VideoProcessingService, $UserWebhookService, $SystemWebhookService, diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 19992a7597..1aa62a9879 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -16,7 +16,7 @@ import type { Config } from '@/config.js'; import { StatusError } from '@/misc/status-error.js'; import { bindThis } from '@/decorators.js'; import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js'; -import { IObject } from '@/core/activitypub/type.js'; +import type { IObject, IObjectWithId } from '@/core/activitypub/type.js'; import { ApUtilityService } from './activitypub/ApUtilityService.js'; import type { Response } from 'node-fetch'; import type { URL } from 'node:url'; @@ -217,7 +217,7 @@ export class HttpRequestService { } @bindThis - public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise { + public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise { const res = await this.send(url, { method: 'GET', headers: { @@ -237,7 +237,7 @@ export class HttpRequestService { // The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match. this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url); - return activity; + return activity as IObjectWithId; } @bindThis diff --git a/packages/backend/src/core/UpdateInstanceQueue.ts b/packages/backend/src/core/UpdateInstanceQueue.ts new file mode 100644 index 0000000000..3fcd215ffa --- /dev/null +++ b/packages/backend/src/core/UpdateInstanceQueue.ts @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable, OnApplicationShutdown } from '@nestjs/common'; +import { CollapsedQueue } from '@/misc/collapsed-queue.js'; +import { bindThis } from '@/decorators.js'; +import { MiNote } from '@/models/Note.js'; +import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; + +type UpdateInstanceJob = { + latestRequestReceivedAt: Date, + shouldUnsuspend: boolean, +}; + +// Moved from InboxProcessorService to allow access from ApInboxService +@Injectable() +export class UpdateInstanceQueue extends CollapsedQueue implements OnApplicationShutdown { + constructor( + private readonly federatedInstanceService: FederatedInstanceService, + ) { + super(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, (id, job) => this.collapseUpdateInstanceJobs(id, job), (id, job) => this.performUpdateInstance(id, job)); + } + + @bindThis + private collapseUpdateInstanceJobs(oldJob: UpdateInstanceJob, newJob: UpdateInstanceJob) { + const latestRequestReceivedAt = oldJob.latestRequestReceivedAt < newJob.latestRequestReceivedAt + ? newJob.latestRequestReceivedAt + : oldJob.latestRequestReceivedAt; + const shouldUnsuspend = oldJob.shouldUnsuspend || newJob.shouldUnsuspend; + return { + latestRequestReceivedAt, + shouldUnsuspend, + }; + } + + @bindThis + private async performUpdateInstance(id: string, job: UpdateInstanceJob) { + await this.federatedInstanceService.update(id, { + latestRequestReceivedAt: new Date(), + isNotResponding: false, + // もしサーバーが死んでるために配信が止まっていた場合には自動的に復活させてあげる + suspensionState: job.shouldUnsuspend ? 'none' : undefined, + }); + } + + @bindThis + async onApplicationShutdown() { + await this.performAllNow(); + } +} diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 1eef85aeef..402d5ab2a4 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -32,7 +32,11 @@ 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 { getApHrefNullable, getApId, getApIds, getApType, getNullableApId, isAccept, isActor, isAdd, isAnnounce, isApObject, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isDislike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; +import InstanceChart from '@/core/chart/charts/instance.js'; +import FederationChart from '@/core/chart/charts/federation.js'; +import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; +import { UpdateInstanceQueue } from '@/core/UpdateInstanceQueue.js'; +import { getApHrefNullable, getApId, getApIds, getApType, getNullableApId, isAccept, isActor, isAdd, isAnnounce, isApObject, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isDislike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost, isActivity, IObjectWithId } from './type.js'; import { ApNoteService } from './models/ApNoteService.js'; import { ApLoggerService } from './ApLoggerService.js'; import { ApDbResolverService } from './ApDbResolverService.js'; @@ -41,7 +45,7 @@ import { ApAudienceService } from './ApAudienceService.js'; import { ApPersonService } from './models/ApPersonService.js'; import { ApQuestionService } from './models/ApQuestionService.js'; import type { Resolver } from './ApResolverService.js'; -import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IDislike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js'; +import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IDislike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost, IActivity } from './type.js'; @Injectable() export class ApInboxService { @@ -88,7 +92,11 @@ export class ApInboxService { private apQuestionService: ApQuestionService, private queueService: QueueService, private globalEventService: GlobalEventService, - private federatedInstanceService: FederatedInstanceService, + private readonly federatedInstanceService: FederatedInstanceService, + private readonly fetchInstanceMetadataService: FetchInstanceMetadataService, + private readonly instanceChart: InstanceChart, + private readonly federationChart: FederationChart, + private readonly updateInstanceQueue: UpdateInstanceQueue, ) { this.logger = this.apLoggerService.logger; } @@ -310,18 +318,19 @@ export class ApInboxService { const targetUri = getApId(activityObject); if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.'; - const target = await resolver.resolve(activityObject).catch(e => { + const target = await resolver.secureResolve(activityObject, uri).catch(e => { this.logger.error(`Resolution failed: ${e}`); throw e; }); if (isPost(target)) return await this.announceNote(actor, activity, target); + if (isActivity(target)) return await this.announceActivity(activity, target, resolver); return `skip: unknown object type ${getApType(target)}`; } @bindThis - private async announceNote(actor: MiRemoteUser, activity: IAnnounce, target: IPost, resolver?: Resolver): Promise { + private async announceNote(actor: MiRemoteUser, activity: IAnnounce, target: IPost & IObjectWithId, resolver?: Resolver): Promise { const uri = getApId(activity); if (actor.isSuspended) { @@ -343,7 +352,9 @@ export class ApInboxService { // Announce対象をresolve let renote; try { - renote = await this.apNoteService.resolveNote(target, { resolver }); + // The target ID is verified by secureResolve, so we know it shares host authority with the actor who sent it. + // This means we can pass that ID to resolveNote and avoid an extra fetch, which will fail if the note is private. + renote = await this.apNoteService.resolveNote(target, { resolver, sentFrom: new URL(getApId(target)) }); if (renote == null) return 'announce target is null'; } catch (err) { // 対象が4xxならスキップ @@ -383,6 +394,63 @@ export class ApInboxService { } } + private async announceActivity(announce: IAnnounce, activity: IActivity & IObjectWithId, resolver: Resolver): Promise { + // Since this is a new activity, we need to get a new actor. + const actorId = getApId(activity.actor); + const actor = await this.apPersonService.resolvePerson(actorId, resolver); + + // Ignore announce of our own activities + // 1. No URI/host on an MiUser == local user + // 2. Local URI on activity == local activity + if (!actor.uri || !actor.host || this.utilityService.isUriLocal(activity.id)) { + throw new Bull.UnrecoverableError(`Cannot announce a local activity: ${activity.id} (from ${announce.id})`); + } + + // Make sure that actor matches activity host. + // Activity host is already verified by resolver when fetching the activity, so that is the source of truth. + const actorHost = this.utilityService.punyHostPSLDomain(actor.uri); + const activityHost = this.utilityService.punyHostPSLDomain(activity.id); + if (actorHost !== activityHost) { + throw new Bull.UnrecoverableError(`Actor host ${actorHost} does not activity host ${activityHost} in activity ${activity.id} (from ${announce.id})`); + } + + // Update stats (adapted from InboxProcessorService) + this.federationChart.inbox(actor.host).then(); + process.nextTick(async () => { + const i = await (this.meta.enableStatsForFederatedInstances + ? this.federatedInstanceService.fetchOrRegister(actor.host) + : this.federatedInstanceService.fetch(actor.host)); + + if (i == null) return; + + this.updateInstanceQueue.enqueue(i.id, { + latestRequestReceivedAt: new Date(), + shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding', + }); + + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.requestReceived(i.host).then(); + } + + this.fetchInstanceMetadataService.fetchInstanceMetadata(i).then(); + }); + + // Process it! + return await this.performOneActivity(actor, activity, resolver) + .finally(() => { + // Update user (adapted from performActivity) + if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + setImmediate(() => { + // Don't re-use the resolver, or it may throw recursion errors. + // Instead, create a new resolver with an appropriately-reduced recursion limit. + this.apPersonService.updatePerson(actor.uri, this.apResolverService.createResolver({ + recursionLimit: resolver.getRecursionLimit() - resolver.getHistory().length, + })); + }); + } + }); + } + @bindThis private async block(actor: MiRemoteUser, activity: IBlock): Promise { // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index b63d4eb2ab..952d1f5219 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -17,7 +17,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import type Logger from '@/logger.js'; import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js'; -import type { IObject } from './type.js'; +import type { IObject, IObjectWithId } from './type.js'; type Request = { url: string; @@ -185,7 +185,7 @@ export class ApRequestService { * @param followAlternate */ @bindThis - public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise { + public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise { const _followAlternate = followAlternate ?? true; const keypair = await this.userKeypairService.getUserKeypair(user.id); @@ -273,6 +273,6 @@ export class ApRequestService { // The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match. this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url); - return activity; + return activity as IObjectWithId; } } diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index f9ccf10fa7..12c3202af1 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -19,11 +19,11 @@ import { fromTuple } from '@/misc/from-tuple.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApLogService, calculateDurationSince, extractObjectContext } from '@/core/ApLogService.js'; import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js'; -import { getApId, getNullableApId, isCollectionOrOrderedCollection } from './type.js'; +import { getApId, getNullableApId, IObjectWithId, isCollectionOrOrderedCollection } from './type.js'; import { ApDbResolverService } from './ApDbResolverService.js'; import { ApRendererService } from './ApRendererService.js'; import { ApRequestService } from './ApRequestService.js'; -import type { IObject, ICollection, IOrderedCollection } from './type.js'; +import type { IObject, ICollection, IOrderedCollection, ApObject } from './type.js'; export class Resolver { private history: Set; @@ -76,6 +76,35 @@ export class Resolver { } } + /** + * Securely resolves an AP object or URL that has been sent from another instance. + * An input object is trusted if and only if its ID matches the authority of sentFromUri. + * In all other cases, the object is re-fetched from remote by input string or object ID. + */ + @bindThis + public async secureResolve(input: ApObject, sentFromUri: string): Promise { + // Unpack arrays to get the value element. + const value = fromTuple(input); + if (value == null) { + throw new IdentifiableError('20058164-9de1-4573-8715-425753a21c1d', 'Cannot resolve null input'); + } + + // This will throw if the input has no ID, which is good because we can't verify an anonymous object anyway. + const id = getApId(value); + + // Check if we can use the provided object as-is. + // Our security requires that the object ID matches the host authority that sent it, otherwise it can't be trusted. + // A mismatch isn't necessarily malicious, it just means we can't use the object we were given. + if (typeof(value) === 'object' && this.apUtilityService.haveSameAuthority(id, sentFromUri)) { + return value as IObjectWithId; + } + + // If the checks didn't pass, then we must fetch the object and use that. + return await this.resolve(id); + } + + public async resolve(value: string | [string]): Promise; + public async resolve(value: string | IObject | [string | IObject]): Promise; @bindThis public async resolve(value: string | IObject | [string | IObject]): Promise { // eslint-disable-next-line no-param-reassign @@ -93,7 +122,7 @@ export class Resolver { } } - private async _resolveLogged(requestUri: string, host: string): Promise { + private async _resolveLogged(requestUri: string, host: string): Promise { const startTime = process.hrtime.bigint(); const log = await this.apLogService.createFetchLog({ @@ -122,7 +151,7 @@ export class Resolver { } } - private async _resolve(value: string, host: string, log?: SkApFetchLog): Promise { + private async _resolve(value: string, host: string, log?: SkApFetchLog): Promise { if (value.includes('#')) { // URLs with fragment parts cannot be resolved correctly because // the fragment part does not get transmitted over HTTP(S). @@ -141,7 +170,7 @@ export class Resolver { this.history.add(value); if (this.utilityService.isSelfHost(host)) { - return await this.resolveLocal(value); + return await this.resolveLocal(value) as IObjectWithId; } if (!this.utilityService.isFederationAllowedHost(host)) { @@ -153,8 +182,8 @@ export class Resolver { } const object = (this.user - ? await this.apRequestService.signedGet(value, this.user) as IObject - : await this.httpRequestService.getActivityJson(value)) as IObject; + ? await this.apRequestService.signedGet(value, this.user) + : await this.httpRequestService.getActivityJson(value)); if (log) { const { object: objectOnly, context, contextHash } = extractObjectContext(object); @@ -199,7 +228,7 @@ export class Resolver { } @bindThis - private resolveLocal(url: string): Promise { + private resolveLocal(url: string): Promise { const parsed = this.apDbResolverService.parseUri(url); if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', `resolveLocal - not a local URL: ${url}`); @@ -214,7 +243,7 @@ export class Resolver { } else { return this.apRendererService.renderNote(note, author); } - }); + }) as Promise; case 'users': return this.usersRepository.findOneByOrFail({ id: parsed.id }) .then(user => this.apRendererService.renderPerson(user as MiLocalUser)); @@ -224,7 +253,7 @@ export class Resolver { this.notesRepository.findOneByOrFail({ id: parsed.id }), this.pollsRepository.findOneByOrFail({ noteId: parsed.id }), ]) - .then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)); + .then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)) as Promise; case 'likes': return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(async reaction => this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, { uri: null }))); @@ -290,7 +319,10 @@ export class ApResolverService { } @bindThis - public createResolver(): Resolver { + public createResolver(opts?: { + // Override the recursion limit + recursionLimit?: number, + }): Resolver { return new Resolver( this.config, this.meta, @@ -308,6 +340,7 @@ export class ApResolverService { this.loggerService, this.apLogService, this.apUtilityService, + opts?.recursionLimit, ); } } diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 5b93543f1e..e7459a57d2 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -39,6 +39,10 @@ export interface IObject { sensitive?: boolean; } +export interface IObjectWithId extends IObject { + id: string; +} + /** * Get array of ActivityStreams Objects id */ @@ -403,6 +407,13 @@ export interface IMove extends IActivity { target: IObject | string; } +export const validActivityTypes = ['Announce', 'Create', 'Update', 'Delete', 'Undo', 'Follow', 'Accept', 'Reject', 'Add', 'Remove', 'Like', 'Dislike', 'EmojiReaction', 'EmojiReact', 'Flag', 'Block', 'Move']; + +export const isActivity = (object: IObject): object is IActivity => { + const type = getApType(object); + return type != null && validActivityTypes.includes(type); +}; + export const isApObject = (object: string | IObject): object is IObject => typeof(object) === 'object'; export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create'; export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete'; diff --git a/packages/backend/src/misc/from-tuple.ts b/packages/backend/src/misc/from-tuple.ts index 366b1e310f..034bae584b 100644 --- a/packages/backend/src/misc/from-tuple.ts +++ b/packages/backend/src/misc/from-tuple.ts @@ -1,4 +1,11 @@ -export function fromTuple(value: T | [T]): T { +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function fromTuple(value: T | [T]): T; +export function fromTuple(value: T | [T] | T[]): T | undefined; +export function fromTuple(value: T | [T] | T[]): T | undefined { if (Array.isArray(value)) { return value[0]; } diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 35a0bf095d..fc7c66591a 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -25,13 +25,14 @@ import { JsonLdService } from '@/core/activitypub/JsonLdService.js'; import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; import { bindThis } from '@/decorators.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { CollapsedQueue } from '@/misc/collapsed-queue.js'; -import { MiNote } from '@/models/Note.js'; +//import { CollapsedQueue } from '@/misc/collapsed-queue.js'; +//import { MiNote } from '@/models/Note.js'; import { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { SkApInboxLog } from '@/models/_.js'; import type { Config } from '@/config.js'; import { ApLogService, calculateDurationSince } from '@/core/ApLogService.js'; +import { UpdateInstanceQueue } from '@/core/UpdateInstanceQueue.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { InboxJobData } from '../types.js'; @@ -43,7 +44,7 @@ type UpdateInstanceJob = { @Injectable() export class InboxProcessorService implements OnApplicationShutdown { private logger: Logger; - private updateInstanceQueue: CollapsedQueue; + //private updateInstanceQueue: CollapsedQueue; constructor( @Inject(DI.meta) @@ -64,9 +65,10 @@ export class InboxProcessorService implements OnApplicationShutdown { private federationChart: FederationChart, private queueLoggerService: QueueLoggerService, private readonly apLogService: ApLogService, + private readonly updateInstanceQueue: UpdateInstanceQueue, ) { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); - this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance); + //this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance); } @bindThis @@ -232,7 +234,7 @@ export class InboxProcessorService implements OnApplicationShutdown { const signerHost = this.utilityService.extractDbHost(authUser.user.uri!); const activityIdHost = this.utilityService.extractDbHost(activity.id); if (signerHost !== activityIdHost) { - throw new Bull.UnrecoverableError(`skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`); + throw new Bull.UnrecoverableError(`skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost})`); } } else { // Activity ID should only be string or undefined. @@ -333,9 +335,7 @@ export class InboxProcessorService implements OnApplicationShutdown { } @bindThis - public async dispose(): Promise { - await this.updateInstanceQueue.performAllNow(); - } + public async dispose(): Promise {} @bindThis async onApplicationShutdown(signal?: string) { diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index f11097b986..d3302dc9bb 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -8,7 +8,7 @@ import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService import type { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import type { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { Resolver } from '@/core/activitypub/ApResolverService.js'; -import type { IObject } from '@/core/activitypub/type.js'; +import type { IObject, IObjectWithId } from '@/core/activitypub/type.js'; import type { HttpRequestService } from '@/core/HttpRequestService.js'; import type { InstanceActorService } from '@/core/InstanceActorService.js'; import type { LoggerService } from '@/core/LoggerService.js'; @@ -25,6 +25,7 @@ import type { } from '@/models/_.js'; import { ApLogService } from '@/core/ApLogService.js'; import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js'; +import { fromTuple } from '@/misc/from-tuple.js'; type MockResponse = { type: string; @@ -72,8 +73,11 @@ export class MockResolver extends Resolver { return this.#remoteGetTrials; } + public async resolve(value: string | [string]): Promise; + public async resolve(value: string | IObject | [string | IObject]): Promise; @bindThis - public async resolve(value: string | IObject): Promise { + public async resolve(value: string | IObject | [string | IObject]): Promise { + value = fromTuple(value); if (typeof value !== 'string') return value; this.#remoteGetTrials.push(value); diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 5767089109..8f1e792829 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -2,6 +2,7 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ + process.env.NODE_ENV = 'test'; import * as assert from 'assert'; @@ -27,10 +28,10 @@ import { DI } from '@/di-symbols.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DownloadService } from '@/core/DownloadService.js'; import { genAidx } from '@/misc/id/aidx.js'; +import { IdService } from '@/core/IdService.js'; import { MockResolver } from '../misc/mock-resolver.js'; import { UserKeypairService } from '@/core/UserKeypairService.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; -import { IdService } from '@/core/IdService.js'; const host = 'https://host1.test'; diff --git a/packages/shared/eslint.config.js b/packages/shared/eslint.config.js index be7a031b63..295829e374 100644 --- a/packages/shared/eslint.config.js +++ b/packages/shared/eslint.config.js @@ -41,6 +41,7 @@ export default [ '@typescript-eslint/prefer-nullish-coalescing': ['warn', { ignorePrimitives: true, }], + 'no-param-reassign': 'off', }, }, ];