Conversation
…rchestrator`, add MQTT service integration, and update dependencies
…es across the codebase
…arity (`$connections` to `$clients`, `$mqttClient` to `$mqtt`) and update all references accordingly
…tClient`, update method names and references for consistency
…ation queue to ensure serialized client creation and destruction
…ties in `MqttService`
…dling, error cases, and payload normalization logic
…ost-message.service`
…eanup, singleton behavior, and service dependency injection
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7004 +/- ##
=======================================
Coverage 76.29% 76.29%
=======================================
Files 405 405
Lines 20348 20348
Branches 4895 4895
=======================================
Hits 15525 15525
Misses 4823 4823
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…ce runtime contract validation, clean up custom disposal logic, and update unit tests
…th `post-message` service
…lated components for consistency.
… enhance error handling; update unit tests to reflect changes.
…-out of connection waiting, and update unit tests accordingly.
…qtt-fe-scaffolding
…e reconnect attempts; enhance error handling, adjust lifecycle logic, and update unit tests accordingly.
# Conflicts: # frontend/src/main.js # frontend/src/services/bootstrap.service.js # frontend/src/services/post-message.service.js # frontend/src/services/service.factory.js # package-lock.json # package.json
| setupReadyPromise(): void | ||
| waitForAppMount(): Promise<void> | ||
| waitForRouterReady(): Promise<void> | ||
| checkUser(): Promise<unknown> |
There was a problem hiding this comment.
Lets not use <unknown> here please - could probably be Promise<void> from looking at the implementation.
| this.$app = app | ||
| this.$router = router | ||
| this.$services = services | ||
| } |
There was a problem hiding this comment.
{ name: string } & CreateServiceOptions - this is an implicit any.
|
|
||
| protected $router: Router = null | ||
|
|
||
| protected $services: ServiceInstances = null |
There was a problem hiding this comment.
protected $app: Maybe<App> = null
protected $router: Maybe<Router> = null
protected $services: Maybe<ServiceInstances> = null
These could utilize the Maybe<T> - that should probably be an exported shared utility type.
export type Maybe<T> = T | null | undefined
| this.$services = services | ||
| class BootstrapService extends BaseService implements BootstrapServiceI { | ||
| protected isReady: boolean = false | ||
| protected readyPromise: Promise<never> | null = null |
| @@ -116,15 +117,7 @@ class BootstrapService { | |||
|
|
|||
| let BootstrapServiceInstance = null | |||
| } | ||
| client.on(eventName, wrapped) | ||
| managed.listeners.add(() => client.off(eventName, wrapped)) | ||
| } |
There was a problem hiding this comment.
Two anys here - could we benefit from a generic in this?
bindManagedListeners (managed: ManagedMqttClient, client: MqttClient) {
const register = <K extends keyof MqttClientEventCallbacks>(
eventName: K,
handler: MqttClientEventCallbacks[K]
) => {
const wrapped = ((...args: Parameters<MqttClientEventCallbacks[K]>) => {
if (managed.destroyed || this.$destroyed || managed.client !== client) return
;(handler as Function)(...args)
}) as MqttClientEventCallbacks[K]
client.on(eventName, wrapped)
managed.listeners.add(() => client.off(eventName, wrapped))
}
register('connect', async () => { /* ... */ })
register('close', () => { /* ... */ })
register('error', (error: Error) => { /* ... */ })
register('message', (topic: string, message: Buffer, packet: IPublishPacket) => { /* ... */ })
}
| } | ||
| } | ||
|
|
||
| let MessagingServiceInstance = null |
| */ | ||
| $app: App = null | ||
|
|
||
| $router: Router = null |
There was a problem hiding this comment.
$app: Maybe<App> = null
$router: Maybe<Router> = null
| } | ||
|
|
||
| // Create a singleton factory instance | ||
| let orchestratorInstance = null |
| } | ||
|
|
||
| async dispose () { | ||
| for (const service of Object.keys(this.$serviceInstances)) { |
There was a problem hiding this comment.
So Object.keys loses the type inference and returns Array<string> - can just cast here. Will yell at you in strict mode.
for (const service of Object.keys(this.$serviceInstances) as Array<keyof ServiceInstances>) {
await this.$serviceInstances[service]?.destroy?.()
// is now BootstrapServiceI | PostMessageServiceI | MqttServiceI | null
this.$serviceInstances[service] = null
}
| { key: 'bootstrap', create: createBootstrapService, requiredLifecycle: ['init', 'destroy'] }, | ||
| { key: 'postMessage', create: createMessagingService, requiredLifecycle: ['destroy'] }, | ||
| { key: 'mqtt', create: createMqttService, requiredLifecycle: ['destroy'] } | ||
| ] |
There was a problem hiding this comment.
You do get an implicit type here but if you want the key to not be string could do this:
export default [
{ key: 'bootstrap' as const, create: createBootstrapService, requiredLifecycle: ['init', 'destroy'] as const },
{ key: 'postMessage' as const, create: createMessagingService, requiredLifecycle: ['destroy'] as const },
{ key: 'mqtt' as const, create: createMqttService, requiredLifecycle: ['destroy'] as const }
]
Without this, key is string and can't safely index ServiceInstances:
for (const serviceDefinition of SERVICE_REGISTRY) {
this.$serviceInstances[serviceDefinition.key] = serviceDefinition.create({ ... })
// .key is a string so cannot index errrr
}
…ugh the serviceOrchestrator
Description
This PR scaffolds frontend MQTT support and restructures service startup/cleanup into a centralized orchestrator, while updating dependent code and adding tests.
Related Issue(s)
closes #6962
Checklist
flowforge.yml?FlowFuse/helmto update ConfigMap TemplateFlowFuse/CloudProjectto update values for Staging/ProductionLabels
area:migrationlabel