React + TypeScript chat UI component powered by Ethora backend APIs and XMPP.
Use it as a standalone chat page, as an embedded widget in your existing app, or as a customizable chat foundation with your own auth and UI.
- Overview
- Why Ethora
- Quick Start
- Integration Modes
- Behavior Notes and Legacy Quirks
- Chat Props Reference
- Full Config Reference (
IConfig) - Custom Widgets and Overrides
- Push Notifications
- Auth Strategies
- Hooks and API Exports
- Use Cases and Feature Coverage
- Hosted vs Self-Host Guidance
- Security Notes
- Reference Architectures
- Use-Case Templates
- Feature Roadmap Snapshot
- Troubleshooting
- Ethora Links and Support
@ethora/chat-component gives you a production-oriented chat interface with:
- Room list and room chat UI
- Message history, replies, reactions, edits, deletes
- Typing indicators
- In-app notifications + Web Push integration
- Configurable auth modes (default/login form/google/jwt/custom user)
- Custom render components for message/input/scroll/day separator/new-message label
The package exports:
Chat(main component)XmppProvideruseUnreadlogoutServiceuseQRCodeChat,handleQRChatIduseInAppNotificationsusePushNotificationsresendMessage
Ethora provides hosted and customizable messaging infrastructure plus a wider product ecosystem.
| Dimension | Ethora Chat Component | Full Ethora Platform |
|---|---|---|
| Primary goal | Embed chat quickly in a React app | End-to-end product stack (chat, profiles, wallets, AI, admin) |
| Time to first chat | Minutes | Higher initial setup, broader capabilities |
| Frontend scope | Focused web chat UI package | Multi-product ecosystem and broader SDK/tooling |
| Custom UI control | High via props + custom components | High, with additional platform-specific tooling |
| Best fit | Support chat, portal messaging, embedded chat widget | Full social/messaging app platforms with extended modules |
| Tool | Command |
|---|---|
| npm | npm i @ethora/chat-component |
| yarn | yarn add @ethora/chat-component |
| pnpm | pnpm add @ethora/chat-component |
| bun | bun add @ethora/chat-component |
import { Chat, XmppProvider } from '@ethora/chat-component';
import './App.css';
export default function App() {
return (
<XmppProvider>
<Chat />
</XmppProvider>
);
}Chat relies on internals that use useXmppClient(). In real integrations, wrap Chat (or your entire app shell) with XmppProvider:
import { Chat, XmppProvider } from '@ethora/chat-component';
export default function App() {
return (
<XmppProvider>
<Chat config={{ baseUrl: 'https://api.chat.ethora.com/v1' }} />
</XmppProvider>
);
}XmppProvider also accepts a pushNotifications prop for headless push setup (works even if Chat is not rendered yet):
import { XmppProvider } from '@ethora/chat-component';
export default function App() {
return (
<XmppProvider
config={{ baseUrl: 'https://api.chat.ethora.com/v1', initBeforeLoad: true }}
pushNotifications={{
enabled: true,
softAsk: false,
vapidPublicKey: 'PLACEHOLDER_VAPID_PUBLIC_KEY',
iconPath: '/icons/push-icon-192.png',
badgePath: '/icons/push-badge-72.png',
firebaseConfig: {
apiKey: 'PLACEHOLDER_API_KEY',
authDomain: 'PLACEHOLDER_AUTH_DOMAIN',
projectId: 'PLACEHOLDER_PROJECT_ID',
storageBucket: 'PLACEHOLDER_STORAGE_BUCKET',
messagingSenderId: 'PLACEHOLDER_MESSAGING_SENDER_ID',
appId: 'PLACEHOLDER_APP_ID',
},
}}
>
{/* Chat can be mounted later or omitted */}
<div>App shell</div>
</XmppProvider>
);
}To avoid duplicated wss://.../ws connections, keep a single XMPP init source:
initBeforeLoad: true->XmppProvideris the only place that initializes XMPP.initBeforeLoad: false->Chat(useChatWrapperInit) initializes XMPP.
If your app also has external client.login(...) logic, guard it:
if (chatConfig.initBeforeLoad) {
console.warn('[XMPP] initBeforeLoad=true, skip external client.login()');
return;
}
await client.login(...);Also pass a memoized config object to both XmppProvider and Chat:
const chatConfig = useMemo(() => ({ ...baseConfig }), [baseConfig]);
<XmppProvider config={chatConfig}>
<Chat config={chatConfig} />
</XmppProvider>;npm run devOpen http://localhost:5173.
All modes below assume XmppProvider wraps Chat.
<XmppProvider>
<Chat />
</XmppProvider>Useful for local proof-of-concept and quick UI validation.
If no googleLogin, no jwtLogin, no userLogin, and no defaultLogin, LoginWrapper currently triggers internal email/password fallback logic.
<XmppProvider>
<Chat config={{ colors: { primary: '#2563eb', secondary: '#dbeafe' } }} />
</XmppProvider><XmppProvider>
<Chat
user={{
email: 'user@example.com',
password: 'PLACEHOLDER_PASSWORD',
}}
/>
</XmppProvider><XmppProvider>
<Chat
config={{
userLogin: {
enabled: true,
user: {
_id: 'PLACEHOLDER_USER_ID',
appId: 'PLACEHOLDER_APP_ID',
walletAddress: 'PLACEHOLDER_WALLET_ADDRESS',
defaultWallet: { walletAddress: 'PLACEHOLDER_WALLET_ADDRESS' },
firstName: 'Jane',
lastName: 'Doe',
xmppPassword: 'PLACEHOLDER_XMPP_PASSWORD',
token: 'PLACEHOLDER_ACCESS_TOKEN',
refreshToken: 'PLACEHOLDER_REFRESH_TOKEN',
username: 'PLACEHOLDER_USERNAME',
},
},
}}
/>
</XmppProvider><XmppProvider>
<Chat
config={{
jwtLogin: {
enabled: true,
token: 'PLACEHOLDER_JWT_TOKEN',
},
}}
/>
</XmppProvider><XmppProvider>
<Chat
config={{
googleLogin: {
enabled: true,
firebaseConfig: {
apiKey: 'PLACEHOLDER_API_KEY',
authDomain: 'PLACEHOLDER_AUTH_DOMAIN',
projectId: 'PLACEHOLDER_PROJECT_ID',
storageBucket: 'PLACEHOLDER_STORAGE_BUCKET',
messagingSenderId: 'PLACEHOLDER_MESSAGING_SENDER_ID',
appId: 'PLACEHOLDER_APP_ID',
},
},
}}
/>
</XmppProvider><XmppProvider>
<Chat
roomJID="ROOM_JID@conference.xmpp.chat.ethora.com"
config={{
setRoomJidInPath: true,
qrUrl: 'https://your-app.example/chat/?qrChatId=',
}}
/>
</XmppProvider>roomJID forces entry room.
setRoomJidInPath syncs room identity to URL path.
useQRCodeChat / handleQRChatId support QR/deep-link room opening.
newArchis now default-on. If omitted, runtime uses new architecture paths.- Old architecture is used only when you explicitly set
config.newArch = false. defaultLogincurrently has legacy inverted behavior inLoginWrapper:- internal fallback login runs when login modes are not configured and
defaultLoginis not set. - keep this in mind when migrating; prefer explicit
userLogin/jwtLogin/googleLogin.
- internal fallback login runs when login modes are not configured and
These are the top-level props accepted by Chat (exported from ReduxWrapper).
| Prop | Type | Required | Notes |
|---|---|---|---|
config |
IConfig |
No | Main behavior/configuration object. |
roomJID |
string |
No | Force specific room JID on load. |
user |
{ email: string; password: string } |
No | Credentials for email/password login helper path. |
loginData |
{ email: string; password: string } |
No | Optional login payload. |
MainComponentStyles |
React.CSSProperties |
No | Outer container style override. |
token |
string |
No | Optional token input (legacy/integration-specific usage). |
CustomMessageComponent |
React.ComponentType<MessageProps> |
No | Replace message bubble rendering. |
CustomInputComponent |
React.ComponentType<SendInputProps & { onSendMessage?; onSendMedia?; placeholderText?; }> |
No | Replace chat input area. |
CustomScrollableArea |
React.ComponentType<CustomScrollableAreaProps> |
No | Replace list/scroll wrapper behavior. |
CustomDaySeparator |
React.ComponentType<DaySeparatorProps> |
No | Replace day separator node. |
CustomNewMessageLabel |
React.ComponentType<NewMessageLabelProps> |
No | Replace "new message" marker. |
Below is a grouped reference for all config options.
| Option | Type | Description |
|---|---|---|
appId |
string |
App identifier for backend context. |
baseUrl |
string |
API base URL (defaults to https://api.chat.ethora.com/v1, the Ethora Cloud production endpoint). |
customAppToken |
string |
Custom app token for API initialization. |
xmppSettings |
{ devServer; host; conference?; xmppPingOnSendEnabled? } |
XMPP connectivity settings. |
initBeforeLoad |
boolean |
Initialize XMPP before normal chat load flow. |
clearStoreBeforeInit |
boolean |
Clear local store before initialization. |
newArch |
boolean |
Defaults to true; set false to explicitly force legacy/old architecture paths. |
useStoreConsoleEnabled |
boolean |
Enable verbose internal logging in console. |
| Option | Type | Description |
|---|---|---|
disableHeader |
boolean |
Hide chat header. |
disableMedia |
boolean |
Disable media sending/processing paths. |
disableRooms |
boolean |
Hide/disable room list area. |
disableRoomMenu |
boolean |
Disable room menu controls. |
disableRoomConfig |
boolean |
Disable room configuration actions. |
disableNewChatButton |
boolean |
Hide new chat/create room action. |
disableUserCount |
boolean |
Hide user count in header/UI. |
disableChatInfo |
{ disableHeader?; disableDescription?; disableType?; disableMembers?; hideMembers?; disableChatHeaderMenu? } |
Fine-grained chat info panel toggles. |
chatHeaderBurgerMenu |
boolean |
Toggle burger menu in chat header. |
chatHeaderSettings |
{ hide?; disableCreate?; disableMenu?; hideSearch? } |
Additional header-level controls. |
chatHeaderAdditional |
{ enabled: boolean; element: any } |
Inject custom element into header area. |
headerLogo |
string | React.ReactElement |
Custom logo in header. |
headerMenu |
() => void |
Custom menu handler. |
headerChatMenu |
() => void |
Custom room header menu handler. |
colors |
{ primary: string; secondary: string } |
Theme colors for component UI. |
roomListStyles |
React.CSSProperties |
Styles for room list pane. |
chatRoomStyles |
React.CSSProperties |
Styles for chat pane. |
backgroundChat |
{ color?: string; image?: string | File } |
Chat background customization. |
bubleMessage |
MessageBubble |
Bubble-level style overrides (as defined in types). |
setRoomJidInPath |
boolean |
Sync room JID to URL path. |
qrUrl |
string |
Base URL for QR deep link behavior. |
| Option | Type | Description |
|---|---|---|
defaultLogin |
boolean |
Legacy quirk: current runtime fallback behavior is inverted; see Behavior Notes section. |
googleLogin |
{ enabled: boolean; firebaseConfig: FBConfig } |
Google login support via Firebase config. |
jwtLogin |
{ token: string; enabled: boolean; handleBadlogin?: React.ReactElement } |
Log user in using JWT exchange flow. |
userLogin |
{ enabled: boolean; user: User | null } |
Inject already-authenticated user directly. |
customLogin |
{ enabled: boolean; loginFunction: () => Promise<User | null> } |
Provide your custom async login function. |
refreshTokens |
{ enabled: boolean; refreshFunction?: () => Promise<{ accessToken: string; refreshToken?: string } | null> } |
Token refresh strategy. |
| Option | Type | Description |
|---|---|---|
defaultRooms |
ConfigRoom[] |
Seed/default rooms. |
customRooms |
{ rooms: PartialRoomWithMandatoryKeys[]; disableGetRooms?: boolean; singleRoom: boolean } |
Fully controlled room source. |
forceSetRoom |
boolean |
Force room setup path in init flow. |
enableRoomsRetry |
{ enabled: boolean; helperText: string } |
Enable retry UX when rooms fail to load. |
| Option | Type | Description |
|---|---|---|
disableInteractions |
boolean |
Disable message interaction menu/actions. |
disableProfilesInteractions |
boolean |
Disable profile interactions from chat UI. |
disableSentLogic |
boolean |
Disable default sent-state logic when needed. |
secondarySendButton |
{ enabled: boolean; messageEdit: string; label?: React.ReactNode; buttonStyles?: React.CSSProperties; hideInputSendButton?: boolean; overwriteEnterClick?: true } |
Extra send action/button config. |
botMessageAutoScroll |
boolean |
Force auto-scroll behavior on bot messages. |
messageTextFilter |
{ enabled: boolean; filterFunction: (text: string) => string } |
Transform/filter outgoing message text. |
eventHandlers |
{ onMessageSent?; onMessageFailed?; onMessageEdited? } |
Lifecycle callbacks for message operations. |
translates |
{ enabled: boolean; translations?: Iso639_1Codes } |
Message translation-related options. |
whitelistSystemMessage |
string[] |
Restrict/render only selected system message types. |
customSystemMessage |
React.ComponentType<MessageProps> |
Replace system message component renderer. |
| Option | Type | Description |
|---|---|---|
disableTypingIndicator |
boolean |
Disable typing indicator UI logic. |
customTypingIndicator |
{ enabled: boolean; text?: string | ((usersTyping: string[]) => string); position?: 'bottom' | 'top' | 'overlay' | 'floating'; styles?: React.CSSProperties; customComponent?: React.ComponentType<{ usersTyping: string[]; text: string; isVisible: boolean; }> } |
Customize typing indicator content and rendering. |
blockMessageSendingWhenProcessing |
boolean | { enabled: boolean; timeout?: number; onTimeout?: (roomJID: string) => void } |
Gate sends while processing in-flight state. |
| Option | Type | Description |
|---|---|---|
inAppNotifications |
{ enabled?; showInContext?; position?; maxNotifications?; duration?; onClick?; customComponent? } |
In-app toast notification behavior and custom rendering. |
| Option | Type | Description |
|---|---|---|
pushNotifications.enabled |
boolean |
Enable browser push subscription flow. |
pushNotifications.vapidPublicKey |
string |
VAPID public key for push registration. |
pushNotifications.firebaseConfig |
FBConfig |
Firebase app config for push messaging. |
pushNotifications.serviceWorkerPath |
string |
Service worker path, default /firebase-messaging-sw.js. |
pushNotifications.serviceWorkerScope |
string |
Service worker scope, default /. |
pushNotifications.iconPath |
string |
Custom icon URL/path for OS push notifications. |
pushNotifications.badgePath |
string |
Custom badge URL/path for OS push notifications. Falls back to iconPath. |
pushNotifications.softAsk |
boolean |
Do not immediately trigger browser permission prompt. |
pushNotifications.onClick |
(params) => void | Promise<void> |
Callback invoked when the user clicks an OS push notification (including cold start via URL marker). |
You can replace key UI parts without forking the package.
| Override prop | Purpose |
|---|---|
CustomMessageComponent |
Fully custom message bubble/row rendering. |
CustomInputComponent |
Custom composer and send controls. |
CustomScrollableArea |
Custom scroll/list container (virtualized or custom behavior). |
CustomDaySeparator |
Custom date separator component. |
CustomNewMessageLabel |
Custom "new message" divider label. |
Example:
import { Chat } from '@ethora/chat-component';
import CustomMessageBubble from './CustomMessageBubble';
import CustomChatInput from './CustomChatInput';
import CustomScrollableArea from './CustomScrollableArea';
import CustomDaySeparator from './CustomDaySeparator';
import CustomNewMessageLabel from './CustomNewMessageLabel';
export default function App() {
return (
<Chat
CustomMessageComponent={CustomMessageBubble}
CustomInputComponent={CustomChatInput}
CustomScrollableArea={CustomScrollableArea}
CustomDaySeparator={CustomDaySeparator}
CustomNewMessageLabel={CustomNewMessageLabel}
config={{
colors: { primary: '#1d4ed8', secondary: '#dbeafe' },
}}
/>
);
}Reference example components in repository:
src/examples/customComponents/CustomMessageBubble.tsxsrc/examples/customComponents/CustomChatInput.tsxsrc/examples/customComponents/CustomScrollableArea.tsxsrc/examples/customComponents/CustomDaySeparator.tsxsrc/examples/customComponents/CustomNewMessageLabel.tsx
| Requirement | Why |
|---|---|
| HTTPS origin (or localhost) | Browser push APIs require secure contexts. |
| Firebase project | FCM token + push transport setup. |
| VAPID public key | Required for web push subscription. |
| Service worker file | Required for background notification handling. |
- Copy service worker into your app's public assets:
npx @ethora/chat-component ethora-chat- Configure push in
config:
<Chat
config={{
pushNotifications: {
enabled: true,
vapidPublicKey: 'PLACEHOLDER_VAPID_PUBLIC_KEY',
firebaseConfig: {
apiKey: 'PLACEHOLDER_API_KEY',
authDomain: 'PLACEHOLDER_AUTH_DOMAIN',
projectId: 'PLACEHOLDER_PROJECT_ID',
storageBucket: 'PLACEHOLDER_STORAGE_BUCKET',
messagingSenderId: 'PLACEHOLDER_MESSAGING_SENDER_ID',
appId: 'PLACEHOLDER_APP_ID',
},
serviceWorkerPath: '/firebase-messaging-sw.js',
serviceWorkerScope: '/',
iconPath: '/icons/push-icon-192.png',
badgePath: '/icons/push-badge-72.png',
softAsk: false,
onClick: async ({ roomJID, messageId, url, data }) => {
// Your app-level routing/analytics can live here.
console.log('Push clicked:', { roomJID, messageId, url, data });
},
},
}}
/>- Optional: use hook directly for controlled permission flow:
import { usePushNotifications } from '@ethora/chat-component';
function PushPermissionButton() {
const { requestPermission } = usePushNotifications({
enabled: true,
softAsk: true,
vapidPublicKey: 'PLACEHOLDER_VAPID_PUBLIC_KEY',
});
return <button onClick={() => requestPermission()}>Enable Push</button>;
}iconPath and badgePath should point to public, reachable assets (for example, files from your app public/ directory).
| Strategy | Config shape | Best for |
|---|---|---|
| Default fallback (legacy quirk) | no auth block / defaultLogin |
Legacy/demo flows; prefer explicit auth modes in production |
| Injected user | userLogin: { enabled: true, user } |
App already has authenticated user/session |
| JWT login | jwtLogin: { enabled: true, token } |
Token-based backend auth flow |
| Google login | googleLogin: { enabled: true, firebaseConfig } |
Google SSO using Firebase |
| Custom login function | customLogin: { enabled: true, loginFunction } |
Fully custom identity provider |
<Chat
config={{
userLogin: {
enabled: true,
user: {
_id: 'PLACEHOLDER_USER_ID',
appId: 'PLACEHOLDER_APP_ID',
walletAddress: 'PLACEHOLDER_WALLET_ADDRESS',
defaultWallet: { walletAddress: 'PLACEHOLDER_WALLET_ADDRESS' },
firstName: 'Jane',
lastName: 'Doe',
xmppPassword: 'PLACEHOLDER_XMPP_PASSWORD',
token: 'PLACEHOLDER_ACCESS_TOKEN',
refreshToken: 'PLACEHOLDER_REFRESH_TOKEN',
username: 'PLACEHOLDER_USERNAME',
},
},
}}
/><Chat
config={{
jwtLogin: {
enabled: true,
token: 'PLACEHOLDER_JWT_TOKEN',
},
refreshTokens: {
enabled: true,
refreshFunction: async () => {
return {
accessToken: 'PLACEHOLDER_NEW_ACCESS_TOKEN',
refreshToken: 'PLACEHOLDER_NEW_REFRESH_TOKEN',
};
},
},
}}
/>| Export | Type | Purpose |
|---|---|---|
Chat |
React component | Main chat component. |
XmppProvider |
React provider | Provides XMPP client context for internal hooks/state. |
useUnread |
hook | Returns unread counters. |
logoutService |
service | Programmatic logout utility. |
useQRCodeChat |
hook | Handle QR-based room links. |
handleQRChatId |
function | Parse/process QR chat ID from URL. |
useInAppNotifications |
hook | Enables and handles in-app notifications. |
usePushNotifications |
hook | Push subscription + foreground handling workflow. |
resendMessage |
function | Retry sending failed/pending messages. |
Basic hook usage:
import { useUnread, logoutService } from '@ethora/chat-component';
function HeaderActions() {
const { totalCount } = useUnread();
return (
<div>
<span>Unread: {totalCount}</span>
<button onClick={() => logoutService.performLogout()}>Logout</button>
</div>
);
}logoutService.performLogout() behavior:
- Dispatches
chatSettingStore/logout - Dispatches
rooms/setLogoutState - Dispatches
roomHeap/clearHeap - Triggers logout middleware, which emits
ethora-xmpp-logout XmppProviderlistens to that event and disconnects active XMPP client
| Area | Status in this package |
|---|---|
| One-room embedded chat | Available |
| Multi-room chat UI | Available |
| Message interactions (reply/copy/edit/delete/report/reactions) | Available |
| Typing indicator | Available |
| Profile interactions in chat | Available (can be disabled) |
| File/media attachments | Available with ongoing enhancements |
| In-app notifications | Available |
| Web push notifications | Available |
| Wallet/assets and extended social modules | Primarily in full Ethora platform |
| Model | Best for | Pros | Tradeoffs |
|---|---|---|---|
| Hosted Ethora backend | Fast time-to-market, smaller teams, MVPs | Fast setup, managed backend operations, easier push/auth onboarding | Less infrastructure-level control |
| Self-hosted Ethora stack | Regulated environments, deep infra control | Full control over infrastructure, compliance customization, internal network deployment options | Higher DevOps/maintenance overhead |
| Hybrid | Gradual migration or split workloads | Can start fast and migrate critical paths later | More architecture complexity |
This is a practical planning snapshot for cross-platform consumers. It is not a release commitment.
| Surface | Current state | Notes |
|---|---|---|
Web React (@ethora/chat-component) |
Available now | This repository. |
| React Native | Via broader Ethora stack | Track platform-specific implementation in Ethora repos/docs. |
| Swift (iOS native) | Planned / ecosystem-level | Confirm status with Ethora team for production timelines. |
| Kotlin (Android native) | Planned / ecosystem-level | Confirm status with Ethora team for production timelines. |
| Flutter | Planned / ecosystem-level | Confirm status with Ethora team for production timelines. |
| Additional roadmap items | Ongoing | Media improvements, richer profile/wallet experiences, broader integration guides. |
| Issue | Likely cause | Fix |
|---|---|---|
useXmppClient must be used within an XmppProvider |
Using internal XMPP-dependent logic without provider context | Wrap the app tree with XmppProvider where needed. |
| Chat loads but no rooms appear | Auth/app context mismatch or room fetching restrictions | Verify appId, user credentials/tokens, baseUrl, and customRooms settings. |
| Push permission never appears | softAsk: true without manual trigger, insecure origin, or missing VAPID key |
Trigger requestPermission(), use HTTPS/localhost, set valid VAPID key. |
| Service worker not found | firebase-messaging-sw.js missing in public dir |
Run npx @ethora/chat-component ethora-chat or copy file manually. |
| Login loop / auth failure | Wrong token/user object shape | Validate jwtLogin, userLogin.user, and refresh token flow. |
- Website: https://ethora.com/
- Try Ethora: https://app.chat.ethora.com/register
- SDK Playground (live): https://playground.chat.ethora.com
- Platform status / uptime: https://uptime.chat.ethora.com
- Chat component docs: https://docs.ethora.com/
- API docs (Swagger, live): https://api.chat.ethora.com/api-docs/#/
- Ethora GitHub hub: https://github.com/dappros/ethora
- This package repo: https://github.com/dappros/ethora-chat-component
- Ethora GitHub organization: https://github.com/dappros
- Forum: https://forum.ethora.com/
- Discord: https://discord.gg/Sm6bAHA3ZC
- Contact: https://ethora.com/#contact
AGPL. See LICENSE.txt.