diff --git a/backend-nest/src/services/host-agent-consumer.service.ts b/backend-nest/src/services/host-agent-consumer.service.ts index f6cf56e..c61b6b1 100644 --- a/backend-nest/src/services/host-agent-consumer.service.ts +++ b/backend-nest/src/services/host-agent-consumer.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { Interval } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -17,7 +17,7 @@ import { License } from '../entities/license.entity'; * staleness sweep marks hosts offline when heartbeats stop arriving. */ @Injectable() -export class HostAgentConsumerService implements OnModuleInit { +export class HostAgentConsumerService implements OnApplicationBootstrap { private readonly logger = new Logger(HostAgentConsumerService.name); /** licenseId -> cache expiry epoch-ms. Positive = exists, absent = unknown. */ @@ -39,7 +39,9 @@ export class HostAgentConsumerService implements OnModuleInit { private readonly licenseRepository: Repository, ) {} - onModuleInit() { + // Bootstrap, not module-init: subscriptions registered before NatsService + // finished connecting silently no-op (see NatsBridgeService note). + onApplicationBootstrap() { this.nats.subscribe('corrosion.*.host.heartbeat', (data, subject) => { const licenseId = subject.split('.')[1]; void this.onHeartbeat(licenseId).catch((err) => diff --git a/backend-nest/src/services/nats-bridge.service.ts b/backend-nest/src/services/nats-bridge.service.ts index c36a673..885198d 100644 --- a/backend-nest/src/services/nats-bridge.service.ts +++ b/backend-nest/src/services/nats-bridge.service.ts @@ -1,14 +1,19 @@ -import { Injectable, OnModuleInit, Logger } from '@nestjs/common'; +import { Injectable, OnApplicationBootstrap, Logger } from '@nestjs/common'; import { NatsService } from './nats.service'; @Injectable() -export class NatsBridgeService implements OnModuleInit { +export class NatsBridgeService implements OnApplicationBootstrap { private readonly logger = new Logger(NatsBridgeService.name); private listeners: Map void>> = new Map(); constructor(private nats: NatsService) {} - onModuleInit() { + // Subscriptions MUST happen in onApplicationBootstrap, not onModuleInit: + // provider onModuleInit order is not guaranteed, and these hooks once ran + // before NatsService connected — every subscribe() silently no-oped and the + // WS bridge was dead from boot. Bootstrap runs after ALL module inits + // (including the awaited NATS connect) complete. + onApplicationBootstrap() { this.nats.subscribe('corrosion.*.companion.heartbeat', (data, subject) => { const licenseId = subject.split('.')[1]; this.emit(licenseId, 'heartbeat', data);