/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Behavior } from "#behavior/Behavior.js"; import { Events as BaseEvents } from "#behavior/Events.js"; import { Endpoint } from "#endpoint/Endpoint.js"; import { Abort, AsyncObservable, AsyncObservableValue, Bytes, Duration, Observable, Observer, Timestamp } from "@matter/general"; import { PeerAddress, type FabricIcd } from "@matter/protocol"; import { SubjectId, VendorId } from "@matter/types"; import { IcdManagement } from "@matter/types/clusters/icd-management"; /** * Controller-side client for ICD (Intermittently Connected Device) Check-In support. * * Auto-installed on a {@link ClientNode} whose peer exposes the IcdManagement cluster. Registration — automatic for a * LIT-operating peer, or explicit via {@link register} — requires an established subscription so the decision runs on a * fresh operating mode rather than a stale cached one. The behavior tracks peer wakefulness to hold interactions for a * sleeping ICD. * * @see {@link MatterSpecification.v161.Core} § 9.15.2, § 9.16 */ export declare class IcdClient extends Behavior { #private; internal: IcdClient.Internal; readonly state: IcdClient.State; readonly events: IcdClient.Events; static readonly early = true; static readonly id = "icd"; get isRegistered(): boolean; /** Whether the peer is LIT-capable (LongIdleTimeSupport feature and specification version >= 1.4.0). */ get peerSupportsLit(): boolean; /** * Whether the peer is currently awake. A non-LIT or not-yet-fed peer has nothing to await and reads true. */ get awake(): any; /** * Wall-clock deadline by which the next Check-In from a registered LIT peer is expected, or undefined when none is * scheduled: no fed peer, not registered, not LIT, or a subscription is held (the peer then reports instead). */ get nextExpectedCheckin(): Timestamp | undefined; initialize(): void; /** * Register this controller as a Check-In client on the peer. * * Installs a shared {@link IcdManagement.RegisterClientRequest.key} on the peer so it can send us encrypted * Check-In messages, records the rolling-counter baseline in {@link IcdClient.State}, and arms the controller-side * Check-In receive path on the fabric. Runs and stores in a transaction of its own, so do not call it from a * transaction that holds the ICD client state. A call while a registration with the same options (defaults * applied) is in flight joins it. {@link Events.registered} follows after the registration is stored. * * @throws {ImplementationError} if a registration exists or is being torn down, one with other options is in * flight, the peer is not online (registration reads from and writes to the peer), or its IcdManagement cluster * lacks the Check-In Protocol feature. * @throws {IcdMultiAdminError} if the peer has more than one administrator from other vendors and * `allowMultiAdmin` is not set — see {@link IcdMultiAdminError.assertSingleAdmin}. * @throws {AbortedError} if unregister(), forget(), decommission or shutdown abandons the registration. * @see {@link MatterSpecification.v161.Core} § 9.16.7.1 */ register(options?: IcdClient.RegisterOptions): Promise; /** * Remove this controller's Check-In registration from the peer and tear down the local receive path. * * Abandons an in-flight key refresh or registration; the peer may already hold the registration, so it is removed * there too. The local registration is cleared once the peer is awake, just before UnregisterClient goes out, so * a failed or abandoned exchange leaves at most a stale entry on the peer, which a later registration replaces. A * no-op when not registered; while an unregister() runs, joins it. Runs in a transaction of its own, so do not * call it from a transaction that holds the ICD client state. {@link Events.unregistered} follows after the * local clear is stored. * * @throws {ImplementationError} while {@link forget} runs, which leaves the peer's registration in place. * @throws {AbortedError} if forget(), decommission or shutdown abandons it before the local registration is * cleared; the registration then stays. * @throws {IcdPeerAsleepError} if a sleeping LIT peer does not wake in time; the registration stays. * @throws The peer's error if UnregisterClient fails after the local registration was cleared. * @see {@link MatterSpecification.v161.Core} § 9.16.7.3 */ unregister(): Promise; /** * Locally drop this controller's Check-In registration without contacting the peer. * * The escape hatch, always allowed: it abandons an in-flight registration, key refresh or an {@link unregister} * that has not cleared the local registration yet. Dropping the fed peer suspends its wakefulness, so * interactions with a sleeping LIT peer no longer hold — a later subscribe can re-establish and re-register. The * peer keeps a stale registration for us until it prunes it or a fresh {@link register} replaces it. A no-op when * nothing is registered or being registered. Runs in a transaction of its own, so do not call it from a * transaction that holds the ICD client state. * * @throws {AbortedError} if decommission or shutdown abandons it first; nothing was forgotten then. * @see {@link MatterSpecification.v161.Core} § 9.16.7.3 */ forget(): Promise; /** * Ask the peer to remain in Active mode for at least `duration` and return the duration it actually promised. * * @see {@link MatterSpecification.v161.Core} § 9.16.7.4 */ stayActive(duration: Duration): Promise; /** Wait for the registration work the destroying edge abandoned; what it stored so far stays. */ [Symbol.asyncDispose](): Promise; } export declare namespace IcdClient { /** Minimum device Matter specification version (BasicInformation, encoded 0xMMmmpprr) for trustworthy LIT behavior. */ const MIN_LIT_SPECIFICATION_VERSION = 17039360; /** * Whether a peer is LIT-capable: advertises LongIdleTimeSupport AND reports Matter specification version >= 1.4.0. * Devices below 1.4.0 (or not reporting a version) are treated as non-LIT regardless of the feature flag. */ function litSupported(endpoint: Endpoint): boolean; /** * Registration, key refresh or teardown in flight. Runtime only; whether a registration exists is the stored * {@link State.key}. At most one runs. */ interface Operation { readonly kind: "registering" | "refreshing" | "unregistering" | "forgetting"; /** Abandons the operation; a child of the behavior's lifetime. */ readonly abort: Abort; /** Settles with the operation, so a second caller can join it. */ done: Promise; /** The options a registration was started with, so a second register() joins only with the same ones. */ readonly options?: RegisterOptions; } /** Options of {@link IcdClient.register}. */ interface RegisterOptions { monitoredSubject?: SubjectId; clientType?: IcdManagement.ClientType; allowMultiAdmin?: boolean; ignoredVendors?: VendorId[]; } class Internal { /** The operation in flight, if any. */ operation?: Operation; /** Aborted when the behavior goes away; every operation's abort is a child of it. */ lifetime: Abort; /** Operations and auto-registrations in flight, so dispose can wait for them. */ operations: Set>; /** Retained across re-registrations so the protocol RX path stays armed without creating a second closure. */ checkInHandler?: FabricIcd.CheckInHandler; /** In-flight LIT auto-registration; tracked so overlapping triggers can't double-register. */ autoRegister?: Promise; /** Set when a trigger arrives while {@link autoRegister} is in flight, so it re-evaluates on completion. */ autoRegisterPending?: boolean; /** * The peer last fed to {@link FabricIcd}. Kept after teardown, when FabricIcd no longer routes its Check-Ins, so * decommission can drop its wakefulness after `peerAddress` is gone. */ peer?: PeerAddress; /** The fed peer's wakefulness `available` observable we currently mirror. */ availableSource?: AsyncObservableValue<[boolean]>; /** Listener mirroring {@link availableSource} into {@link IcdClient.State.available}; created once and moved on re-feed. */ availableListener?: Observer<[boolean]>; /** The fed peer's wakefulness `checkInMissed` observable we currently mirror. */ checkInMissedSource?: AsyncObservable<[]>; /** Listener mirroring {@link checkInMissedSource} into {@link IcdClient.Events.checkInMissed}; created once and moved on re-feed. */ checkInMissedListener?: Observer<[]>; } class State { /** * Shared secret installed on the peer at registration, used to verify Check-In message ICD counters. */ key?: Bytes; /** * Key offered to the peer by a key refresh that neither a response nor a Check-In under it has confirmed yet. */ pendingKey?: Bytes; /** * Starting value of the peer's ICD counter for {@link key}; Check-In counters are validated relative to this. * * The ICDCounter of the peer's RegisterClient response. When a Check-In under a refreshed key confirms it first, * the last counter accepted under the previous key, a lower bound of the peer's counter when it took the key; a * response that follows replaces it. */ counterStart?: number; /** * Offset of the most recently observed Check-In counter from {@link counterStart}. */ lastOffset?: number; /** * Subject the peer monitors on our behalf (the node ID notified on Check-In). */ monitoredSubject?: SubjectId; /** * Client type registered with the peer (permanent or ephemeral). */ clientType?: IcdManagement.ClientType; /** * Whether this controller is currently registered as a Check-In client on the peer: true exactly while a * {@link key} is stored. * * @deprecated Use {@link IcdClient.isRegistered} or test {@link key}. */ registered: boolean; /** * Time of the most recently received Check-In from the peer. */ lastCheckInReceivedAt?: Timestamp; /** * Whether the registered peer is reachable: a subscription to it is held, or its next Check-In is not yet * overdue. Non-LIT peers are always available. */ available: boolean; } /** * A listener's error is logged, so it neither keeps the other listeners from the event nor, for {@link checkedIn}, * rolls back the Check-In state stored with it. */ class Events extends BaseEvents { /** Emits after a registration is stored. */ registered: Observable; /** Emits after the cleared registration is stored, by unregister(), forget() or decommission. */ unregistered: Observable; /** Emits for each accepted Check-In, inside the transaction that stores its counter. */ checkedIn: Observable<[checkIn: { counter: number; activeModeThreshold: number; }], void>; /** * Emits once per key refresh, after the new key is stored as confirmed (by the RegisterClient response or by * the first Check-In under it). */ keyRefreshed: Observable; /** Emits after commit whenever {@link State.key} changes; {@link registered}, {@link unregistered} and {@link keyRefreshed} follow from it. */ key$Changed: Observable<[value: AllowSharedBufferSource | undefined, oldValue: AllowSharedBufferSource | undefined], void>; available$Changed: Observable<[value: boolean, oldValue: boolean], void>; /** * Emits after commit whenever {@link State.counterStart} changes: set by a registration, replaced when a key * refresh is confirmed and again when a response follows a Check-In that confirmed it, cleared when the * registration is. */ counterStart$Changed: Observable<[value: number | undefined, oldValue: number | undefined], void>; /** Emits when a registered LIT peer misses its expected Check-In (its availability window lapsed). */ checkInMissed: Observable; } } //# sourceMappingURL=IcdClient.d.ts.map