add-channel-connect-button
GitHub在 Novu SDK 中为新聊天渠道构建连接按钮组件,涵盖 SolidJS 核心、React 包装器、数据层及注册配置。
Trigger Scenarios
Install
npx skills add novuhq/novu --skill add-channel-connect-button -g -y
SKILL.md
Frontmatter
{
"name": "add-channel-connect-button",
"description": "Build a new channel Connect button (e.g. WhatsApp, Discord, LINE) in @novu\/js and @novu\/react following the existing SlackConnectButton, MsTeamsConnectButton, and TelegramConnectButton pattern. Use when adding connect\/disconnect UI for a new chat channel\/provider to the SDK — covering the SolidJS core component, the React wrapper, the channelConnections (OAuth) vs channelEndpoints (deep-link) data layer, the component registry, and package exports."
}
Add a Channel Connect Button
Build <Channel>ConnectButton for a new chat provider, mirroring Slack, MsTeams,
and Telegram. The button lets a subscriber connect (open OAuth popup / deep link)
and disconnect a channel, with loading + connected states.
Architecture (3 layers)
packages/js (SolidJS core) packages/react (public wrapper)
───────────────────────── ──────────────────────────────
<Channel>ConnectButton.tsx ◄──── Default<Channel>ConnectButton.tsx (Mounter → mountComponent)
uses a data hook <Channel>ConnectButton.tsx (memo → NovuUI → withRenderer)
calls novu.channel* SDK
registered in Renderer.tsx
The React component mounts the SolidJS component by name through novuUI.mountComponent.
That name must be registered in packages/js/src/ui/components/Renderer.tsx.
Step 0 — Pick the connection model (do this first)
This single decision drives which SDK module, hook, and props you use.
| Connection-based — like Slack, MS Teams | Endpoint-based — like Telegram | |
|---|---|---|
| Use when | Provider authorizes at workspace/tenant level via OAuth | Subscriber links by opening a deep link; no workspace auth |
| SDK module | novu.channelConnections |
novu.channelEndpoints |
| Connect call | generateConnectOAuthUrl() → OAuth popup URL |
link({ integrationIdentifier }) → deep-link URL |
| Detect / poll | get(connectionIdentifier) |
list({ providerId, integrationIdentifier, limit: 1 }) |
| Data hook | reuse useChannelConnection |
new use<Channel>Connection (list-based, copy useTelegramConnection) |
| Extra props | connectionIdentifier, context, scope, connectionMode, autoLinkUser |
none beyond the base props |
| Identifier | buildDefaultConnectionIdentifier(...) from components/constants.ts |
not needed |
If unsure: OAuth/app-install provider → connection-based; bot deep-link/token provider → endpoint-based.
File checklist
Copy the nearest sibling for your model (Slack/MS Teams = connection, Telegram = endpoint), then rename. Full templates: see reference.md.
Core — packages/js
- CREATE
src/ui/icons/<Channel>Colored.tsx— brand icon (copyTelegramColored.tsx) - CREATE
src/ui/components/<channel>-connect-button/<Channel>ConnectButton.tsx - CREATE (endpoint model only)
src/ui/api/hooks/use<Channel>Connection.ts - EDIT
src/ui/components/Renderer.tsx— import it, add tonovuComponents, add the name string toCHANNEL_COMPONENTS - EDIT
src/ui/components/index.ts—export * from './<channel>-connect-button/<Channel>ConnectButton' - EDIT
src/ui/index.ts— re-export<Channel>ConnectButtonPropsin the type block
React — packages/react
- CREATE
src/components/<channel>-connect-button/Default<Channel>ConnectButton.tsx - CREATE
src/components/<channel>-connect-button/<Channel>ConnectButton.tsx - EDIT
src/components/index.ts—export * from './<channel>-connect-button/<Channel>ConnectButton' - EDIT
src/index.ts— add the component to the value export block and<Channel>ConnectButtonPropsto the type block
Usually NOT needed
- The
channelConnections/channelEndpointsSDK modules already cover both models. Only add a new method underpackages/js/src/channel-*if the provider needs a brand-new server call (rare). Backend lives inapps/api/src/app/channel-connections/. - Never edit
libs/internal-sdk(auto-generated).
Button behavior contract
Every connect button implements the same state machine — keep it identical:
- Initial load:
<Show when={!loading()} fallback={<Loader/>}>; deriveisConnected()from the hook's connection/endpoint, andisLoading() = loading() || actionLoading(). - Click when connected →
disconnect(identifier)→onDisconnectSuccess()/onDisconnectError(err). - Click when not connected →
setActionLoading(true), get the URL,window.open(url, '_blank', 'noopener,noreferrer'), then start polling. - Polling → fixed interval (
2500ms,120_000mstimeout) or backoff (see MS Teams). Use a one-shotcommittedflag / ref so only the first success-or-timeout fires side effects. Clear the timer inonCleanup. - Resolve → success:
mutate(found)+onConnectSuccess(identifier); timeout:onConnectError(new Error(...)). - Appearance → reuse the shared keys
channelConnectButtonContainer | Button | Inner | Icon | Label, each passed{ connected }context. Icons go throughIconRendererWrapper(keyschannelConnect/channelConnected) with a fallback to<Channel>Colored/CheckCircleFill. Do not add new appearance keys — they are shared across all connect buttons. - Base props →
integrationIdentifier(required),subscriberId?,onConnectSuccess?,onConnectError?,onDisconnectSuccess?,onDisconnectError?,connectLabel?,connectedLabel?.
Conventions & gotchas
- The core is SolidJS, not React: signals over hooks,
<Show>over ternaries, read props lazily (() => props.x), cleanup viaonCleanup. The React layer is just a mounting shim. - The
nameinmountComponent({ name: '<Channel>ConnectButton' })must equal the key innovuComponentsand be listed inCHANNEL_COMPONENTS, or it renders through the wrong path. <Channel>ConnectButtonPropsis defined in the core and imported by React from@novu/js/ui— do not redefine it.- Novu conventions: lowercase-dashed dirs, named exports, blank line before every
return, no nested ternaries. - These packages are published — new exports are a minor bump; keep prop changes additive/backward-compatible.
Build & verify
pnpm build(required after changingpackages/).- Type-check + lint the touched files.
- Manual test: add a page/tab in
playground/nextjs(copysrc/components/telegram-end-user-connect.tsx) that wraps<Channel>ConnectButtonin<NovuProvider>, then connect/disconnect against a real integration.
Version History
- 56a8a16 Current 2026-08-29 05:10


