---
title: Usage
summary: SDM interceptors let you insert shared logic into SmartDeviceModel init, request, and response chains. This guide focuses on when to use logger, dp-kit, and matter-kit, and how to write custom interceptors.
questions:
  - When should you use SDM interceptors, and when are hooks enough?
  - What problems are best handled in the init, request, and response chains?
  - What are the differences between logger, dp-kit, and matter-kit?
  - Why does the default logger disappear after manually configuring an interceptor chain?
  - In what order do multiple interceptors run, and why should dp-kit usually come before matter-kit?
  - What is the difference between onInit and init.initDpState / init.initDevInfo?
  - What do ctx, next, and data represent in a custom interceptor?
  - When should you choose PublishDpsInterceptor instead of OnDpDataChangeInterceptor?
---

# SDM interceptors

<Alert type="warning">
Custom interceptors and their type contracts are still experimental. You can use `logger`, `dp-kit`, and `matter-kit` directly, but if you implement your own interceptors, expect possible API adjustments in future versions.
</Alert>

`interceptors` let you insert shared logic into SDM initialization, DP sending, and event callback chains. They are useful for logging, protocol conversion, structured state mapping, and conditional short-circuiting.

## When do you need interceptors?

✅ **Good fit**:
- You need shared logic, logging, or analytics before or after `publishDps`
- You need to convert raw device reports into business-friendly data
- You want to reuse `logger`, `dp-kit`, or `matter-kit`
- Multiple pages depend on the same initialization or protocol processing logic

❌ **Probably unnecessary**:
- The logic is only for one page and does not need to be reused
- You only read simple DP features and `useProps / useActions` is enough
- You do not need to change the SDM init, send, or event chains

## How do you choose a built-in interceptor?

| Interceptor | Best for | Core capability | Docs |
| ----- | ----- | ----- | ----- |
| `logger` | Development debugging | Prints logs for `init / request / response` chains | [logger](/en/miniapp/solution-panel/ability/common/sdm/interceptors/logger) |
| `dp-kit` | Complex DP features | Protocol parsing, structured state, send options (`sendOptions`), optimistic UI updates | [dp-kit](/en/miniapp/solution-panel/ability/common/sdm/interceptors/dpkit/usage) |
| `matter-kit` | Matter lighting devices | Matter ↔ standard DP conversion; can be composed with `dp-kit` | [matter-kit](/en/miniapp/solution-panel/ability/common/sdm/interceptors/matterkit/usage) |

<Alert type="info">
SDM mounts `logger` on built-in chains by default. But once you manually configure an interceptor array for a specific chain, that chain is replaced by your new array. If you still want logs, add `logger` back explicitly.
</Alert>

## Integration guide

### Step 1: Choose the interception point

| Category | Available entries | Typical use |
| ----- | ----- | ----- |
| `init` | `initDpState`, `initDevInfo` | Adjust `dpState` or `devInfo` during initialization |
| `request` | `publishDps` | Format outgoing data, append params, throttle or debounce sends |
| `response` | `onDpDataChange`, `onDeviceInfoUpdated`, `onDeviceOnlineStatusUpdate`, `onNetworkStatusChange`, `onBluetoothAdapterStateChange` | Transform reports, sync state, or enhance events |
| `onInit` | `(device) => void` | Extra logic after device initialization, such as `dpKit.init(device)` |

### Step 2: Mount interceptors on SmartDeviceModel

> `src/devices/index.ts`

```ts | pure
import { SmartDeviceModel, createDpKit, logger } from '@ray-js/panel-sdk';
import { defaultSchema } from '@/devices/schema';

type SmartDeviceSchema = typeof defaultSchema;

const dpKit = createDpKit<SmartDeviceSchema>({
  sendDpOption: {
    immediate: true,
  },
});

const options = {
  interceptors: {
    onInit: (device) => {
      dpKit.init(device);
    },
    init: {
      initDpState: [logger, ...dpKit.interceptors.init.initDpState],
      initDevInfo: [logger, ...dpKit.interceptors.init.initDevInfo],
    },
    request: {
      publishDps: [logger, ...dpKit.interceptors.request.publishDps],
    },
    response: {
      onDpDataChange: [logger, ...dpKit.interceptors.response.onDpDataChange],
      onDeviceInfoUpdated: [logger, ...dpKit.interceptors.response.onDeviceInfoUpdated],
      onDeviceOnlineStatusUpdate: [logger],
      onNetworkStatusChange: [logger],
      onBluetoothAdapterStateChange: [logger],
    },
  },
};

export const devices = {
  common: new SmartDeviceModel<SmartDeviceSchema>(options),
};
```

If you only use one built-in kit, you can also pass `interceptors: dpKit.interceptors` or `interceptors: matterKit.interceptors` directly.

### Step 3: Order multiple interceptors correctly

- Interceptors in the same chain wrap in array order; earlier items receive the input first
- If an interceptor does not call `next(...)`, later interceptors and the underlying method will not run
- When combining `dp-kit` and `matter-kit` for `publishDps`, `dp-kit` usually goes first and `matter-kit` second

```ts | pure
request: {
  publishDps: [
    ...dpKit.interceptors.request.publishDps,
    ...matterKit.interceptors.request.publishDps,
  ],
},
```

## Custom interceptors

### Common types

| Type | Chain | Purpose |
| ----- | ----- | ----- |
| `Interceptor` | Generic | A quick way to define a general-purpose interceptor |
| `InitDpStateInterceptor` | `init.initDpState` | Shared processing around `dpState` initialization |
| `InitDevInfoInterceptor` | `init.initDevInfo` | Shared processing around `devInfo` initialization |
| `PublishDpsInterceptor` | `request.publishDps` | Rewrite outgoing params, append DP features, or change options |
| `OnDpDataChangeInterceptor` | `response.onDpDataChange` | Convert raw device reports into business state |
| `OnDeviceInfoUpdatedInterceptor` | `response.onDeviceInfoUpdated` | React to device info updates |
| `OnDeviceOnlineStatusUpdateInterceptor` | `response.onDeviceOnlineStatusUpdate` | React to online/offline updates |
| `OnNetworkStatusChangeInterceptor` | `response.onNetworkStatusChange` | React to network changes |
| `OnBluetoothAdapterStateChangeInterceptor` | `response.onBluetoothAdapterStateChange` | React to Bluetooth state changes |

> For group scenarios, there are also `OnGroupDpDataChangeInterceptor` and `OnGroupInfoChangeInterceptor`. This guide focuses on the most common single-device SDM chains.

### Execution model

<Alert type="info">
Interceptors use a three-layer curried shape: `ctx => next => data`.
</Alert>

| Parameter | Description |
| ----- | ----- |
| `ctx.type` | The current chain name, such as `publishDps` or `onDpDataChange` |
| `ctx.log` | The logger for the current chain, supporting `log.info / log.warn / log.fatal` |
| `ctx.instance` | The current `SmartDeviceModel` instance, which can access `getDpState()`, `getDevInfo()`, and more |
| `next` | The next interceptor or the underlying method; the chain continues only if you call it |
| `data` | The current request input or event payload |

```ts | pure
const interceptor = (ctx) => (next) => (data) => {
  return next(data);
};
```

### Common patterns

**1. Append or rewrite data before sending**

```ts | pure
import { PublishDpsInterceptor } from '@ray-js/panel-sdk';

export const appendExtraDp: PublishDpsInterceptor<SmartDeviceSchema> =
  (ctx) => (next) => (dpState, options) => {
    const nextDpState = {
      ...dpState,
      power_go: !ctx.instance.getDpState().power_go,
    };

    ctx.log.info('append extra dp before publishDps', nextDpState, 'appendExtraDp');

    return next(nextDpState, options);
  };
```

**2. Convert raw reports into business data**

```ts | pure
import { DpState, DpValue, OnDpDataChangeInterceptor } from '@ray-js/panel-sdk';

export const mapDpsToDpState: OnDpDataChangeInterceptor<
  SmartDeviceSchema,
  DpState
> = (ctx) => (next) => (data) => {
  const devInfo = ctx.instance.getDevInfo();
  const dpState = {} as DpState;

  Object.keys(data.dps).forEach((dpId) => {
    dpState[devInfo.idCodes[dpId]] = data.dps[dpId] as DpValue;
  });

  return next(dpState);
};
```

**3. Short-circuit the rest of the chain**

```ts | pure
import { Interceptor } from '@ray-js/panel-sdk';

export const skipWhenReadonly: Interceptor<SmartDeviceSchema> =
  (ctx) => (next) => (data) => {
    const isReadonlyMode = true;

    if (isReadonlyMode) {
      ctx.log.info('skip current interceptor chain', data, 'skipWhenReadonly');
      return null;
    }

    return next(data);
  };
```

## Further reading

### logger

> Integration details and the type definition of the default logging interceptor

[logger docs](/en/miniapp/solution-panel/ability/common/sdm/interceptors/logger)

### dp-kit

> Full guide to complex DP parsing, `sendOptions`, and structured hooks

[dp-kit usage guide](/en/miniapp/solution-panel/ability/common/sdm/interceptors/dpkit/usage)

### matter-kit

> Matter lighting DP mapping and composition with other interceptors

[matter-kit usage guide](/en/miniapp/solution-panel/ability/common/sdm/interceptors/matterkit/usage)

### SmartDeviceModel

> `interceptors` are ultimately mounted on SmartDeviceModel; see the API doc for the full initialization options

[SmartDeviceModel init API](/en/miniapp/solution-panel/ability/common/sdm/api/init)
