/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Behavior } from "#behavior/Behavior.js"; import { ClusterBehavior } from "#behavior/cluster/ClusterBehavior.js"; import { DescriptorClient } from "#behaviors/descriptor"; import { Endpoint } from "#endpoint/Endpoint.js"; import { EndpointType } from "#endpoint/type/EndpointType.js"; import type { Node } from "#node/Node.js"; import { Environment, Environmental, Logger, Observable } from "@matter/general"; import { DeviceTypeId } from "@matter/types"; const logger = Logger.get("ClientStructureEvents"); /** * An environmental service that manages events endpoint and behavior types. */ export class ClientStructureEvents { #endpointEvents?: Map>; #clusterEvents?: Map< string, Array<{ requestedType: Behavior.Type; event: Observable<[endpoint: Endpoint, type: ClusterBehavior.Type]>; }> >; #clusterReplaced?: Observable<[endpoint: Endpoint, type: ClusterBehavior.Type]>; #clusterDeleted?: Observable<[endpoint: Endpoint, type: ClusterBehavior.Type]>; #structureApplied?: Observable<[node: Node]>; static [Environmental.create](env: Environment) { const instance = new ClientStructureEvents(); env.set(ClientStructureEvents, instance); return instance; } endpointInstalled(type: T): Observable<[endpoint: Endpoint]> { if (this.#endpointEvents === undefined) { this.#endpointEvents = new Map(); } let event = this.#endpointEvents.get(type.deviceType); if (event === undefined) { this.#endpointEvents.set(type.deviceType, (event = this.#createEvent("endpointInstalled"))); } return event as Observable<[endpoint: Endpoint]>; } clusterInstalled(type: T): Observable<[endpoint: Endpoint, type: T]> { if (this.#clusterEvents === undefined) { this.#clusterEvents = new Map(); } let events = this.#clusterEvents.get(type.id); if (events === undefined) { this.#clusterEvents.set(type.id, (events = [])); } for (const { requestedType, event } of events) { if (requestedType.supports(type)) { return event; } } const event = this.#createEvent("clusterInstalled"); events.push({ requestedType: type, event }); return event; } get clusterReplaced() { if (this.#clusterReplaced) { return this.#clusterReplaced; } return (this.#clusterReplaced = this.#createEvent("clusterReplaced")); } get clusterDeleted() { if (this.#clusterDeleted) { return this.#clusterDeleted; } return (this.#clusterDeleted = this.#createEvent("clusterDeleted")); } /** * Emits after a node's structure has applied a batch of data (an interaction, wire changes or its cache), once every * endpoint and cluster event of that batch has been emitted. */ get structureApplied(): Observable<[node: Node]> { if (this.#structureApplied) { return this.#structureApplied; } return (this.#structureApplied = this.#createEvent("structureApplied")); } emitEndpoint(endpoint: Endpoint) { if (this.#endpointEvents && endpoint.behaviors.supported.descriptor) { const deviceTypes = endpoint.stateOf(DescriptorClient).deviceTypeList; for (const dt of deviceTypes ?? []) { this.#endpointEvents.get(dt.deviceType)?.emit(endpoint); } } for (const type of Object.values(endpoint.behaviors.supported)) { if (!("cluster" in type)) { continue; } this.emitCluster(endpoint, type as ClusterBehavior.Type); } for (const part of endpoint.parts) { this.emitEndpoint(part); } } emitCluster(endpoint: Endpoint, type: ClusterBehavior.Type) { const events = this.#clusterEvents?.get(type.id); if (!events) { return; } for (const { requestedType, event } of events) { if (type.supports(requestedType)) { event.emit(endpoint, type); } } } emitClusterReplaced(endpoint: Endpoint, type: ClusterBehavior.Type) { if (!this.#clusterReplaced) { return; } this.#clusterReplaced.emit(endpoint, type); } emitClusterDeleted(endpoint: Endpoint, type: ClusterBehavior.Type) { if (!this.#clusterDeleted) { return; } this.#clusterDeleted.emit(endpoint, type); } emitStructureApplied(node: Node) { this.#structureApplied?.emit(node); } #createEvent(kind: string): Observable { return Observable(unhandledError); function unhandledError(e: unknown) { logger.error(`Unhandled error in client structure ${kind} event handler:`, e); } } }