/** * @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 type { ClientNode } from "#node/ClientNode.js"; import { ClassExtends, Duration, Observable, ServerAddress, Timestamp } from "@matter/general"; import type { SupportedTransportsBitmap, Val } from "@matter/protocol"; import { CommissioningMode, ControllerCommissioningFlow, ControllerCommissioningFlowOptions, DeviceAttestationValidator, DiscoveryData, Fabric, FabricAuthority, Peer, PeerTimingParameters, PeerAddress as ProtocolPeerAddress, SessionParameters as ProtocolSessionParameters, Subscribe } from "@matter/protocol"; import { CaseAuthenticatedTag, DeviceTypeId, FabricIndex, NodeId, VendorId } from "@matter/types"; import { RemoteDescriptor } from "./RemoteDescriptor.js"; /** * Client functionality related to commissioning. * * Updates node state based on commissioning status and commissions new nodes. */ export declare class CommissioningClient extends Behavior { #private; internal: CommissioningClient.Internal; readonly state: CommissioningClient.State; readonly events: CommissioningClient.Events; static readonly early = true; static readonly id = "commissioning"; /** * Older versions stored a peer's session parameters under `sessionParameters`: the full effective set once a * session existed, otherwise only the DNS-SD intervals. The first becomes the reported set, the second the * advertised intervals. Values already under the new keys win. */ static migrate(values: Val.Struct): void; initialize(options: { descriptor?: RemoteDescriptor; }): void; [Symbol.asyncDispose](): void; commission(passcode: number | string): Promise; commission(options: CommissioningClient.CommissioningOptions): Promise; /** * Remove this node from the fabric. * * After removal the {@link ClientNode} remains intact. You can use {@link ClientNode#delete} to remove the node * permanently. * * Only legal if this node controls the peer's fabric. */ decommission(): Promise; /** * Open a Basic Commissioning Window (uses the passcode originally printed on the device). * * This is an optional feature and not all devices support it; use {@link openEnhancedCommissioningWindow} unless * you specifically need the basic commissioning method. */ openBasicCommissioningWindow(commissioningTimeout?: Duration): Promise; /** * Open an Enhanced Commissioning Window using a freshly generated random passcode. * * The peer's BasicInformation must be seeded — its vendor/product IDs are encoded into the QR pairing code — so * this throws (before opening any window) if it is not. * * @returns the pairing codes and the values they encode. */ openEnhancedCommissioningWindow(commissioningTimeout?: Duration): Promise; /** * Override to implement CASE commissioning yourself. * * If you override, matter.js commissions to the point where commissioning over PASE is complete. You must then * complete commissioning yourself by connecting to the device and invoking the "CommissioningComplete" command. */ protected finalizeCommissioning(_address: ProtocolPeerAddress, _discoveryData?: DiscoveryData, _sessionParameters?: Partial): Promise; get descriptor(): RemoteDescriptor | undefined; set descriptor(descriptor: RemoteDescriptor | undefined); } export declare namespace CommissioningClient { /** * The pairing codes of an open Enhanced Commissioning Window and the values they encode. * * @see {@link MatterSpecification.v161.Core} § 5.1 * @see {@link MatterSpecification.v161.Core} § 11.19.8.1 */ interface EnhancedCommissioningWindow { manualPairingCode: string; qrPairingCode: string; passcode: number; /** The long (12-bit) discriminator the device advertises while the window is open. */ discriminator: number; vendorId: VendorId; productId: number; /** How long the window stays open, in whole seconds as sent to the device. */ commissioningTimeout: Duration; } /** * Concrete version of {@link ProtocolPeerAddress}. */ class PeerAddress implements ProtocolPeerAddress { fabricIndex: FabricIndex; nodeId: NodeId; constructor(fabricIndex: FabricIndex, nodeId: NodeId); } /** * Supported transport flags. */ class SupportedTransports implements Partial { tcpClient?: boolean; tcpServer?: boolean; } /** * Session intervals a node advertises via DNS-SD (SII, SAI and SAT). * * @see {@link MatterSpecification.v161.Core} § 4.3.4 */ class AdvertisedIntervals { idleInterval?: Duration; activeInterval?: Duration; activeThreshold?: Duration; } /** * Concrete version of {@link ProtocolSessionParameters}. */ class SessionParameters implements Partial { idleInterval?: Duration; activeInterval?: Duration; activeThreshold?: Duration; dataModelRevision?: number; interactionModelRevision?: number; specificationVersion?: number; maxPathsPerInvoke?: number; supportedTransports?: SupportedTransports; maxTcpMessageSize?: number; } /** * The network address of a node. */ class NetworkAddress { type: "udp" | "tcp" | "ble"; ip?: string; port?: number; peripheralAddress?: string; ttl?: Duration | undefined; discoveredAt?: Timestamp | undefined; constructor(address: NetworkAddress); } class Internal { peerObserver?: Disposable; peerUpdated?: (peer: Peer) => void; } class State { /** * Fabric index and node ID for paired peers. If this is undefined the node is uncommissioned. */ peerAddress?: PeerAddress; /** * Case Authenticated Tags (CATs) * * See {@link PeerDescriptor} */ caseAuthenticatedTags?: readonly CaseAuthenticatedTag[]; /** * Known network addresses for the device. If this is undefined, the node has not been located on any network * interface. */ addresses?: ServerAddress[]; /** * The host named by the SRV record of the device's commissionable advertisement, as the * responder wrote it. * * Operational discovery does not report a host, so this stays what commissioning found while * {@link addresses} goes on being refreshed. * * @see {@link MatterSpecification.v161.Core} § 4.3.1 */ hostname?: string; /** * Time at which the device was discovered. */ discoveredAt?: Timestamp; /** * Time at which we discovered the device's current operational addresses. */ onlineAt?: Timestamp; /** * Time at which we concluded the device's current operational address is unreachable. */ offlineAt?: Timestamp; /** * Time at which the device was commissioned. */ commissionedAt?: Timestamp; /** * Fabric index the peer assigned to our identity (from the AddNoc response). Substituted for the * {@link FabricIndex.OMIT_FABRIC} sentinel on fabric-scoped struct writes so the local cache mirrors * the value the peer will store. * * Distinct from {@link peerAddress.fabricIndex} which is our local fabric index. */ fabricIndexOnPeer?: FabricIndex; /** * The TTL of the discovery record if applicable (in seconds). */ ttl?: Duration; /** * The canonical global ID of the device. */ deviceIdentifier?: string; /** * The device's long discriminator. */ discriminator?: number; /** * The last know commissioning mode of the device. */ commissioningMode?: CommissioningMode; /** * Vendor. */ vendorId?: VendorId; /** * Product. */ productId?: number; /** * Advertised device type. */ deviceType?: DeviceTypeId; /** * The advertised device name specified by the user. */ deviceName?: string; /** * An optional manufacturer-specific unique rotating ID for uniquely identifying the device. */ rotatingIdentifier?: string; /** * A bitmap indicating how to transition the device to commissioning mode from its current state. */ pairingHint?: number; /** * Textual pairing instructions associated with pairing hint. */ pairingInstructions?: string; /** * The session parameters the remote node reported in its most recent session. */ reportedSessionParameters?: SessionParameters; /** * The session intervals the remote node advertises via DNS-SD. */ advertisedIntervals?: AdvertisedIntervals; /** * Set when a TCP session to the remote node reported no TCP server support; a later session reporting it clears * the flag. */ tcpUnsupported?: boolean; /** * TCP support bitmap. */ tcpSupport?: number; /** * Indicates whether node is ICD with a slow (15 s+) polling interval. */ longIdleTimeOperatingMode?: boolean; } class Events extends BaseEvents { peerAddress$Changed: Observable<[value: ProtocolPeerAddress | undefined, oldValue: ProtocolPeerAddress | undefined], void>; addresses$Changed: Observable<[value: ServerAddress[] | undefined, oldValue: ServerAddress[] | undefined], void>; caseAuthenticatedTags$Changed: Observable<[value: CaseAuthenticatedTag[] | undefined, oldValue: CaseAuthenticatedTag[] | undefined], void>; } /** * Options that control commissioning. */ interface BaseCommissioningOptions { /** * The ID to assign the node during commissioning. By default the node receives the next available ID. */ nodeId?: NodeId; /** * The fabric the node joins upon commissioning. Defaults to the default fabric of the assigned * {@link FabricAuthority}. */ fabric?: Fabric; /** * The authority controlling the commissioning fabric. Defaults to the {@link FabricAuthority} of the local * environment. */ fabricAuthority?: FabricAuthority; /** * Custom commissioning flow implementation to use instead of the default. */ commissioningFlowImpl?: ClassExtends; /** * Abort signal for cancellation. With {@link ClientNode#commission} it cancels the PASE attempt. With * `peers.commission()` it cancels the whole discovery as `Discovery.Options.abort` describes. */ abort?: AbortSignal; /** * Called immediately after PASE is established, before the main commissioning flow begins. * * Return `true` to proceed with commissioning (this candidate won the parallel race). * Return `false` to abort cleanly — the PASE session is closed and commissioning stops. * * When omitted, commissioning always proceeds. */ continueCommissioningAfterPase?: () => boolean; /** * Overall wall-clock budget for PASE establishment. * Defaults to 30 seconds. */ timeout?: Duration; /** * The initial read/subscription used to populate node data. * * By default, matter.js reads all attributes on the node. This allows us to efficiently initialize the complete * node structure. * * If you only require a subset of attributes you can replace this with a more discriminative read. For * example, if you are only interested in interacting with the root endpoint and the On/Off cluster on other * endpoints, you could do: * * ```js * { * defaultSubscription: Read( * Read.Attribute({ endpoint: 0 }), * Read.Attribute({ cluster: OnOffCluster }) * ) * } * ``` * * Note that certain clusters like Descriptor and Basic Information contain critical operational data. If your * read omits them then the node will only be partially functional once initialized. A built read is sent as * given; see {@link NetworkClient.State.defaultSubscription}. */ defaultSubscription?: Subscribe; /** * By default, nodes we commission are automatically subscribed to using the {@link defaultSubscription} (or a * full wildcard subscription if that is undefined). * * Matter.js will not subscribe automatically if set to false. */ autoSubscribe?: boolean; /** * By default a newly-commissioned node performs a one-time read of its structure so its state is available * immediately, even when {@link autoSubscribe} is false. Set to false to skip this read and leave the node * uninitialized until a later subscription or read. */ autoStateInitialize?: boolean; /** * Case Authenticated Tags (CATs) */ caseAuthenticatedTags?: CaseAuthenticatedTag[]; /** * Controls behavior when device attestation produces findings. * See {@link ControllerCommissioningFlowOptions.onAttestationFailure} for details. */ onAttestationFailure?: DeviceAttestationValidator.OnAttestationFailure; /** * WiFi network credentials to configure on the device during commissioning. Required if the device connects * to the network over WiFi and doesn't already have credentials configured. */ wifiNetwork?: ControllerCommissioningFlowOptions["wifiNetwork"]; /** * Thread network credentials to configure on the device during commissioning. Required if the device connects * to the network over Thread and doesn't already have credentials configured. */ threadNetwork?: ControllerCommissioningFlowOptions["threadNetwork"]; /** * The regulatory location (indoor or outdoor) where the device is used. * Defaults to `Indoor` if not provided. */ regulatoryLocation?: ControllerCommissioningFlowOptions["regulatoryLocation"]; /** * The two-character country code where the device is deployed (e.g. "DE", "US"). * Defaults to "XX" (unspecified). */ regulatoryCountryCode?: ControllerCommissioningFlowOptions["regulatoryCountryCode"]; /** * Override the final operational step of commissioning. * * The flow runs PASE, arm-failsafe, CSR and AddNOC as usual, then invokes this hook **instead of** connecting * to the device operationally and sending "CommissioningComplete" itself, and awaits it. The hook owns the * finalization and must drive it to completion before resolving: resolve on success, throw on failure. * `commission()` resolves or rejects with this hook's outcome — a throw rolls the commissioning back. * * For a delegated/split flow, hand `address.nodeId`, `discoveryData` and `sessionParameters` (those the device * reported over PASE) to the controller that will finish (typically a separate, network-side controller sharing * this fabric) and **await** its `serverNode.peers.completeCommissioning(nodeId, discoveryData, * sessionParameters)` from within this hook. Do not return before that completes: the PASE session is held * open across the hook and the device's failsafe stays armed until "CommissioningComplete" arrives, so * returning early leaves the device mid-commission and it reverts, discarding the freshly issued NOC. See * docs/MIGRATION_CONTROLLER_018.md for the full recipe. */ finalizeCommissioning?: (address: ProtocolPeerAddress, discoveryData?: DiscoveryData, sessionParameters?: Partial) => Promise; /** * Timing overrides for the step-18 CASE reconnect. * * After commissioning the commissioner establishes the first operational CASE session. The device is freshly * online at that point so tighter timing is appropriate. Any fields provided here are merged on top of the * global peer timing defaults for that single connection only. * * Defaults to `{ delayBeforeNextAddress: Seconds(15), maxDelayBetweenInitialContactRetries: Seconds(60), delayAfterPeerError: Minutes(2) }`. */ caseConnectionTiming?: Partial; /** * Wall-clock budget for the step-18 CASE reconnect, across every candidate address and retry. * * Defaults to `DEFAULT_CASE_CONNECTION_TIMEOUT`, which leaves room for two server-side retry windows. Lower it * to bound commissioning to a single handshake attempt, so a handshake the device fails is a commissioning * failure rather than something a retry can recover. * * On an IP transport, raising it past the failsafe armed for this step (up to 5 minutes for the reconnect, * clamped by the device's own `MaxCumulativeFailsafeSeconds`) has the device roll `addNOC` back mid-connect, so a * longer budget cannot succeed; the concurrent BLE flow keeps re-arming instead. A non-finite budget is * rejected. * * Expiry fails commissioning, which deletes the peer and so aborts the connection it was still attempting. The * rejection therefore reports a budget that ran out, not what the device answered. No effect where * {@link finalizeCommissioning} owns the operational step, nor on the connection * `peers.completeCommissioning()` makes. */ caseConnectionTimeout?: Duration; } interface PasscodeOptions extends BaseCommissioningOptions { /** * The device's passcode. */ passcode: number; /** * The device's long discriminator. */ discriminator?: number; /** * The vendor the onboarding payload names, if it names one. * * A commissionable advertisement may state its own vendor and product (§ 4.3.1's `VP` record). * Where both the payload and the advertisement name one, a device that disagrees is not the * device the payload describes and is passed over — otherwise a code for one device silently * onboards another sharing its discriminator. */ vendorId?: VendorId; /** The product the onboarding payload names, matched as {@link vendorId} is. */ productId?: number; } interface PairingCodeOptions extends BaseCommissioningOptions { /** * The device's pairing code. */ pairingCode: string; } type CommissioningOptions = PasscodeOptions | PairingCodeOptions; function PasscodeOptions(options: T): T & PasscodeOptions; } //# sourceMappingURL=CommissioningClient.d.ts.map